## 本文参考
[浅谈STM32之GPIO - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/242615058)
[STM32的8种GPIO输入输出模式深入详解](https://blog.csdn.net/baidu_37366055/article/details/80060962)
[STM32 之 HAL库_戈扬的博客-CSDN博客_hal库](https://blog.csdn.net/xuzhexing/article/details/90137754)
[【STM32 HAL库】HAL库的简介及文件结构](https://www.bilibili.com/read/cv3170341/)
[从IIC实测波形入手,搞懂IIC通信](https://zhuanlan.zhihu.com/p/161710767)
[位图(bmp)文件格式分析_Wanda && Aidem _bmp文件](https://blog.csdn.net/aidem_brown/article/details/80500637)
感谢所有以上公开的博客,如果有侵犯版权,请评论,我会立即删除
## github地址
**本文的github对应仓库地址是 [1outOfMemory1/stm32Experiment: (github.com)](https://github.com/1outOfMemory1/stm32Experiment)**
## stm32学习
### 基础内容
#### stm32 生成的三种可执行文件
.axf axf是可以通过编译器 直接download的可执行文件
.sct 分散加载能够将加载和运行时存储器中的代码和数据描述在被称为分散加载描述文件的一个文本描述文件中,以供连接时使用。
.hex 通过串口下载的可执行文件
#### 1 STM32的三种开发方式
通常新手在入门STM32的时候,首先都要先选择一种要用的开发方式,不同的开发方式会导致你编程的架构是完全不一样的。一般大多数都会选用标准库和HAL库,而极少部分人会通过直接配置寄存器进行开发。
##### **一、直接配置寄存器**
不少先学了51的朋友可能会知道,会有一小部分人或是教程是通过汇编语言直接操作寄存器实现功能的,这种方法到了STM32就变得不太容易行得通了,因为STM32的寄存器数量是51单片机的十数倍,如此多的寄存器根本无法全部记忆,开发时需要经常的翻查芯片的数据手册,此时直接操作寄存器就变得非常的费力了。但还是会有很小一部分人,喜欢去直接操作寄存器,因为这样更接近原理,知其然也知其所以然。
##### **二、标准库**
上面也提到了,STM32有非常多的寄存器,而导致了开发困难,所以为此ST公司就为每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx…之类的。在这些 .c .h文件中,包括一些常用量的宏定义,把一些外设也通过结构体变量封装起来,如GPIO口时钟等。所以我们只需要配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能。也是目前最多人使用的方式,也是学习STM32接触最多的一种开发方式,我也就不多阐述了。
##### **三、HAL库**
HAL库是ST公司目前主力推的开发方式,全称就是Hardware Abstraction Layer(抽象印象层)。库如其名,很抽象,一眼看上去不太容易知道他的作用是什么。它的出现比标准库要晚,但其实和标准库一样,都是为了节省程序开发的时期,而且HAL库尤其的有效,如果说标准库把实现功能需要配置的寄存器集成了,那么HAL库的一些函数甚至可以做到某些特定功能的集成。也就是说,同样的功能,标准库可能要用几句话,HAL库只需用一句话就够了。并且HAL库也很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相通外设,意思也就是不能无中生有,例如F7比F3要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,绝大多数都可以直接复制粘贴。是而且使用ST公司研发的STMcube软件,可以通过图形化的配置功能,直接生成整个使用HAL库的工程文件,可以说是方便至极,但是方便的同时也造成了它执行效率的低下,在各种论坛帖子真的是被吐槽的数不胜数。
HAL是 Hardware Abstraction Layer 的缩写,中文名:硬件抽象层。HAL 库是 ST 为 STM32 最新推出的抽象层嵌入式软件,可以更好的确保跨 STM32 产品的最大可移植性。该库提供了一整套一致的中间件组件,如 RTOS,USB,TCP/IP 和 图形 等。
HAL 库是基于一个非限制性的 BSD 许可协议(Berkeley Software Distribution)而发布的开源代码。 ST 制作的中间件堆栈(USB 主机和设备库,STemWin)带有允许轻松重用的许可模式, 只要是在 ST 公司的 MCU 芯片上使用,库中的中间件(USB 主机/设备库,STemWin)协议栈即被允许随便修改,并可以反复使用。至于基于其它著名的开源解决方案商的中间件(FreeRTOS,FatFs,LwIP和PolarSSL)也都具有友好的用户许可条款。
#### 启动文件
```shell
启动文件
startup_stm32f10x_ld.s ld: low-density 小容量, FLASH 容量在 16-32K 之间
startup_stm32f10x_md.s md: medium-density 中容量,FLASH 容量在 64-128K 之间
startup_stm32f10x_hd.s hd: high-density 中容量, FLASH 容量在 256-512K 之间
startup_stm32f10x_xl.s xl: 超大容量, FLASH 容量在 512-1024K 之间
以上四种都属于基本型,包括 STM32F101xx、STM32F102xx、STM32F103xx 系列
startup_stm32f10x_cl.s cl:connectivity line devices 互联型,特指 STM32F105xx 和
STM32F107xx 系列
startup_stm32f10x_ld_vl.s
startup_stm32f10x_md_vl.s vl:value line devices 超值型系列,特指 STM32F100xx 系列
startup_stm32f10x_hd_vl.s
```
#### 固件库(标准库)的内容(理解)
```c++
1. 汇编编写的启动文件
startup_stm32f10x_hd.s 设置堆栈指针,设置pc指针、初始化终端向量表,配置系统时钟、用对C库函数_main最终到达main.c c语言程序
2. 时钟配置文件
system_stm32f10x.c 将外部时钟HSE=8M,经过PLL倍频
3. 外设相关的
stm32f10x.h 实现了内核里边寄存器的映射
xxx 可以表示 GPIO、USRAT3、I2C、SPI、FSMC等等
stm32f10x_xxx.c 外设的驱动函数库文件
stm32f10x_xxx.h 存放外设的初始化结构体,外设初始化结构体成员的参数列表,外设固件库函数的声明
4. 内核相关的
core.cm3.h 实现了内核里边的外设寄存器映射
core.cm3.c
NVIC(嵌套向量中断控制器) 、SysTick(系统滴答定时器)
misc.h
misc.c
头文件的头文件
stm32f10x_conf.h
专门存放中断服务函数的c文件 中断服务函数其实可以放在其他文件中
stm32f10x_it.c
stm32f10x_it.h
system_stm32f10x.h 的时钟头文件
```
#### 固件库(标准库)的使用
![image-20210726090043001](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726090043001.png)
```shell
下载网站 (需要注册st公司账号)
https://my.st.com/content/my_st_com/zh/products/embedded-software/mcu-mpu-embedded-software/stm32-embedded-software/stm32-standard-peripheral-libraries.html
+---Libraries
├─CMSIS
│ │ CMSIS debug support.htm
│ │ CMSIS_changes.htm
│ │ License.doc
│ ├─CM3
│ │ ├─CoreSupport
│ │ │ core_cm3.c
│ │ │ core_cm3.h
│ │ └─DeviceSupport
│ │ └─ST
│ │ └─STM32F10x
│ │ │ Release_Notes.html
│ │ │ stm32f10x.h
│ │ │ system_stm32f10x.c
│ │ │ system_stm32f10x.h
│ │ │
│ │ └─startup
│ │ ├─arm
│ │ │ startup_stm32f10x_cl.s
│ │ │ startup_stm32f10x_hd.s
│ │ │
│ │ ├─gcc_ride7
│ │ │ startup_stm32f10x_cl.s
│ │ │ startup_stm32f10x_hd.s
│ │ │
│ │ ├─iar
│ │ │ startup_stm32f10x_cl.s
│ │ │ startup_stm32f10x_hd.s
│ │ │
│ │ └─TrueSTUDIO
│ │ startup_stm32f10x_cl.s
│ │ startup_stm32f10x_hd.s
│ │
│ └─Documentation
│ CMSIS_Core.htm
│
└─STM32F10x_StdPeriph_Driver
| │ Release_Notes.html
| │
| ├─inc
| │ misc.h
| │ stm32f10x_adc.h
| │
| └─src
| misc.c
| stm32f10x_adc.c
+---Project
| +---STM32F10x_StdPeriph_Examples # stm32官方给出的实例程序 有很多模块的 比如I2c ADC等等 省略了很多内容
| | +---ADC # ADC模块的实例代码
| \---STM32F10x_StdPeriph_Template # 工程模板 各个模板
| +---EWARM
| +---HiTOP
| +---MDK-ARM # 如果你使用keil 这个是很重要的参考模板
| +---RIDE
| \---TrueSTUDIO
+---Utilities
| \---STM32_EVAL
\---_htmresc
```
##### 项目模板文件 (标准库)
```shell
F:\PROJECT\GITHUB\UNFINISHED\STM32EXPERIMENT\SRC\TEMPLATE
│ keilkill.bat # 删除编译文件 执行之后会删除上一次编译产生的中间文件
│
├─Doc
│ readme.txt # 说明文件
│
├─Libraries
│ ├─CMSIS #内核相关的 、启动文件
│ │ │ core_cm3.c
│ │ │ core_cm3.h
│ │ │ stm32f10x.h
│ │ │ system_stm32f10x.c
│ │ │ system_stm32f10x.h
│ │ │
│ │ └─startup
│ │ startup_stm32f10x_cl.s
│ │ startup_stm32f10x_hd.s
│ │ startup_stm32f10x_hd_vl.s
│ │ startup_stm32f10x_ld.s
│ │ startup_stm32f10x_ld_vl.s
│ │ startup_stm32f10x_md.s
│ │ startup_stm32f10x_md_vl.s
│ │ startup_stm32f10x_xl.s
│ │
│ └─STM32F10x_StdPeriph_Driver # 外设相关
│ ├─inc
│ │ misc.h
│ │ stm32f10x_adc.h
│ │ stm32f10x_bkp.h
│ │ stm32f10x_can.h
│ │ stm32f10x_cec.h
│ │ stm32f10x_crc.h
│ │ stm32f10x_dac.h
│ │ stm32f10x_dbgmcu.h
│ │ stm32f10x_dma.h
│ │ stm32f10x_exti.h
│ │ stm32f10x_flash.h
│ │ stm32f10x_fsmc.h
│ │ stm32f10x_gpio.h
│ │ stm32f10x_i2c.h
│ │ stm32f10x_iwdg.h
│ │ stm32f10x_pwr.h
│ │ stm32f10x_rcc.h
│ │ stm32f10x_rtc.h
│ │ stm32f10x_sdio.h
│ │ stm32f10x_spi.h
│ │ stm32f10x_tim.h
│ │ stm32f10x_usart.h
│ │ stm32f10x_wwdg.h
│ │
│ └─src
│ misc.c
│ stm32f10x_adc.c
│ stm32f10x_bkp.c
│ stm32f10x_can.c
│ stm32f10x_cec.c
│ stm32f10x_crc.c
│ stm32f10x_dac.c
│ stm32f10x_dbgmcu.c
│ stm32f10x_dma.c
│ stm32f10x_exti.c
│ stm32f10x_flash.c
│ stm32f10x_fsmc.c
│ stm32f10x_gpio.c
│ stm32f10x_i2c.c
│ stm32f10x_iwdg.c
│ stm32f10x_pwr.c
│ stm32f10x_rcc.c
│ stm32f10x_rtc.c
│ stm32f10x_sdio.c
│ stm32f10x_spi.c
│ stm32f10x_tim.c
│ stm32f10x_usart.c
│ stm32f10x_wwdg.c
│
├─Project # arm keil工程文件
│ test.uvprojx
│
└─User #真正的用户写的文件
main.c
stm32f10x_conf.h
stm32f10x_it.c
stm32f10x_it.h
system_stm32f10x.c
```
#### hal库
HAL全称Hardware Abstract Layer,意为硬件抽象层,HAL库是STM32开发生态中极为重要的组成部分,不过它不单独提供,而是以STM32CubeMX拓展包的形式提供。拓展包中除HAL库之外,也有Fatfs、FreeRTOS、STemWin等组件,可以在STM32CubeMX中选择性的使用。与标准外设库相比,HAL库封装得显得更加紧凑,并且源代码通过对外设的对象化,使“层”的特点非常明显,大部分外设也都通过句柄操作,极难见到寄存器的影子。
##### 目录结构
文件夹中的东西会因工程不同而不同,但是基本思想一致,只是少一些多一些外设文件罢了,注意这个目录结构是**定时器(用到了定时器和串口)**项目中的目录,仅作为举例。
```shell
# 注意这个目录结构是定时器(用到了定时器和串口)项目中 hal库中的目录结构
# 很多东西有缺少 比如一些外设文件 因为没有用上 所以stm32cubemx没有生成 但这个比较偏重基础 我就没有用复杂工程的
│ .mxproject
│ 11timer.ioc
│
├───Core
│ ├───Inc
│ │ gpio.h
│ │ main.h
│ │ stm32f1xx_hal_conf.h
│ │ stm32f1xx_it.h
│ │ tim.h
│ │ usart.h
│ │
│ └───Src
│ gpio.c # 输入输出外设配置
│ main.c # 主函数
│ stm32f1xx_hal_msp.c
│ stm32f1xx_it.c # 异常服务函数与中断服务函数的入口
│ system_stm32f1xx.c
│ tim.c # 定时器配置
│ usart.c # 串口配置
│
├───Drivers
│ ├───CMSIS
│ │ ├───Device
│ │ │ └───ST
│ │ │ └───STM32F1xx
│ │ │ ├───Include
│ │ │ │ stm32f103xe.h
│ │ │ │ stm32f1xx.h # 对STM32外设的定义
│ │ │ │ system_stm32f1xx.h
│ │ │ │
│ │ │ └───Source
│ │ │ └───Templates
│ │ └───Include
│ │ cmsis_armcc.h # 兼容不同的编译器 为core_cm7.h文件提供一些编译符号和汇编级的内核接口
│ │ cmsis_armclang.h # 兼容不同的编译器 为core_cm7.h文件提供一些编译符号和汇编级的内核接口
│ │ cmsis_compiler.h # 兼容不同的编译器 为core_cm7.h文件提供一些编译符号和汇编级的内核接口
│ │ cmsis_gcc.h # 兼容不同的编译器 为core_cm7.h文件提供一些编译符号和汇编级的内核接口
│ │ cmsis_iccarm.h
│ │ cmsis_version.h
│ │ core_armv8mbl.h
│ │ core_armv8mml.h
│ │ core_cm0.h
│ │ core_cm0plus.h
│ │ core_cm1.h
│ │ core_cm23.h
│ │ core_cm3.h
│ │ core_cm33.h
│ │ core_cm4.h
│ │ core_cm7.h
│ │ core_sc000.h
│ │ core_sc300.h
│ │ mpu_armv7.h
│ │ mpu_armv8.h
│ │ tz_context.h
│ │
│ └───STM32F1xx_HAL_Driver
│ ├───Inc # 以下是一些常见的外设 比如rcc 实时时钟 dma 直接内存读取 flash 闪存芯片等等的驱动
│ │ │ stm32f1xx_hal.h
│ │ │ stm32f1xx_hal_cortex.h
│ │ │ stm32f1xx_hal_def.h
│ │ │ stm32f1xx_hal_dma.h
│ │ │ stm32f1xx_hal_dma_ex.h
│ │ │ stm32f1xx_hal_exti.h
│ │ │ stm32f1xx_hal_flash.h
│ │ │ stm32f1xx_hal_flash_ex.h
│ │ │ stm32f1xx_hal_gpio.h
│ │ │ stm32f1xx_hal_gpio_ex.h
│ │ │ stm32f1xx_hal_pwr.h
│ │ │ stm32f1xx_hal_rcc.h
│ │ │ stm32f1xx_hal_rcc_ex.h
│ │ │ stm32f1xx_hal_tim.h
│ │ │ stm32f1xx_hal_tim_ex.h
│ │ │ stm32f1xx_hal_uart.h
│ │ │
│ │ └───Legacy
│ │ stm32_hal_legacy.h
│ │
│ └───Src
│ stm32f1xx_hal.c
│ stm32f1xx_hal_cortex.c
│ stm32f1xx_hal_dma.c
│ stm32f1xx_hal_exti.c
│ stm32f1xx_hal_flash.c
│ stm32f1xx_hal_flash_ex.c
│ stm32f1xx_hal_gpio.c
│ stm32f1xx_hal_gpio_ex.c
│ stm32f1xx_hal_pwr.c
│ stm32f1xx_hal_rcc.c
│ stm32f1xx_hal_rcc_ex.c
│ stm32f1xx_hal_tim.c
│ stm32f1xx_hal_tim_ex.c
│ stm32f1xx_hal_uart.c
│
└───MDK-ARM # 这个文件夹中
11timer.uvguix.yhn
11timer.uvoptx
11timer.uvprojx
JLinkSettings.ini
startup_stm32f103xe.lst
startup_stm32f103xe.s
```
##### 目录结构解析
1. 首先是cmsis开头的`cmsis_armcc.h、cmsis_armclang.h、cmsis_compiler.h和cmsis_gcc.h`四个文件,这四个文件用于**兼容不同的编译器**,并为core_cm7.h文件提供一些编译符号和汇编级的内核接口 ,位置\Drivers\CMSIS\Include。
2. 接下来是`core_cm1.h`文件,它对Cortex-M1(**原博主是f7 我用的是f1**)的内核硬件进行了简单的封装,并向上级文件提供了一些简单的接口,位置**\Drivers\CMSIS\Include。**
3. 在Cortex-M1内核之上的是STM32f1xx的设备硬件,对应于文件`stm32f1xx.h`,并在`stm32hf1x.h`中细分,STM32f103ZET6对应于stm32f1xx.h。stm32f1xx.h的内容大致类似于标准外设库的stm32fxxx.h,是对STM32外设的定义。`stm32f1xx.h`的位置位于
**Drivers\CMSIS\Device\ST\STM32F1xx\Include**
4. 位于` MDK-ARM\startup_stm32f103xe.s`,这是一个汇编代码文件,**为程序的直接入口**,**负责分配所有异常处理函数及中断服务函数的入口地址,也负责在复位及硬件就绪后调用main函数**。
5. `system_stm32f1xx.c/.h`是一组介于底层硬件和上层HAL库之间的文件启动,其中源文件使用了一些由`stm32f1xx.h`与`core_cm7.h`提供的定义,如SCB,RCC。**并提供了一个系统初始化函数SystemInit()**,**用于初始化硬件**。这个函数在main()函数调用前由`startup_stm32f103xe.s`调用。位于**core/src**
6. 在HAL库中,由一些文件不属于STM32f1外设驱动部分,他们是`stm32f1xx_hal.c/.h、stm32f1xx_hal_cortex.c/.h`以及`stm32_hal_legacy.h、stm32f1xx_hal_def.h`。`stm32f1xx_hal.c/.h`是HAL驱动文件,主要负责初始化HAL库的运行环境,向上层提供HAL管理的API。`stm32f1xx_hal_cortex.c/.h`是Cortex内核的驱动文件,对Cortex-M1内核外设进行了封装,向上层提供了MPU、NVIC、SysTick的管理API。`stm32_hal_legacy.h`为提供给STM32CubeMX用于兼容老版本HAL库的文件,仅仅重新定义了一些宏和常量,使HAL库便于维护。`stm32f1xx_hal_def.h`定义了一些HAL的宏、枚举和结构。
7. 至于用户文件,文件夹中共7个文件:`main.c/.h、stm32f1xx_it.c/.h、stm32f1xx_hal_msp.c、system_stm32f1xx.c、stm32f1xx_hal_conf.h、tim.h/.c、usart.h/.c、gpio.h/.c。 ` `main.c/.h`不用多说,用户主程序。 `stm32f1xx_it.c/.h`为**异常服务函数与中断服务函数的入口**,其中所有的入口都在`startup_stm32f103xe.s`中有定义,并引出至该文件。 `system_stm32f1xx.c`上方提过 。stm32f1xx_hal_conf.h文件 时钟适应参数的其它内容一般不做改动 。而`tim.h/.c、usart.h/.c`是项目特有的,不同项目不一样,因为这个是定时器项目,用到了定时器和串口所以会有这两个文件。
#### gpio引脚的8个工作模式
![image-20210726091934283](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726091934283.png)
##### 输入模式
-1. 输入浮空(GPIO_Mode_IN_FLOATING)
-输入上拉(GPIO_Mode_IPU)
-输入下拉(GPIO_Mode_IPD)
-模拟输入(GPIO_Mode_AIN)
###### 1输入浮空
![image-20210726104301937](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726104301937.png)
###### 2上拉输入
一般我们设计输入的时候(拿按键输入举例子),键按下时单片机检测到低电平,按键松开时检测到高电平,这个高电平就是通过上拉电阻实现的:如果在设计硬件电路的时候,**忘记了设计上下拉电阻**,该怎么办呢?stm32的GPIO口基本上都配有内部上下拉电阻,通过寄存器控制,可以将某个引脚设置为上下拉模式: ![[公式]](https://www.zhihu.com/equation?tex=V_%7BDD%7D) 通过一个电阻连接到电路中,这个![[公式]](https://www.zhihu.com/equation?tex=V_%7BDD%7D)给电路提供了一个高电平,也就是说,外部I/O引脚不确定高低电平时,上拉输入的电路上一直都是高电平。
![image-20210726104422628](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726104422628.png)
![image-20210726103730760](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726103730760.png)
###### 3下拉输入
这里面的 ![[公式]](https://www.zhihu.com/equation?tex=V_%7BSS%7D) 其实就是地,不光是这里,学过数电的应该很清楚这个![[公式]](https://www.zhihu.com/equation?tex=V_%7BSS%7D) 了。那么这个图也就很明确了。通过一个电阻将电路接地,也就是说下来输入,在I/O引脚的高低电平不确定的情况下,电路上则始终时低电平。
![image-20210726104508592](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726104508592.png)
###### 4模拟输入
可以看到模拟输入,**是直接连接外部I/O引脚的(绕开了触发器)**。也就是说他将外部接口提供的连续的模拟量原封不动的输入到片上外设上去了**(其实输入到ADC上面)**。
![image-20210726104544932](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726104544932.png)
###### 补充 复用功能输入
![image-20210726091934283](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726091934283.png)
从上图可以看的很清楚,复用功能输入相比于模拟输入,其实只是多接了一个TTL肖特基触发器。**但是正是这点差别而导致千差万别,换句话说,复用功能输入其实输入到片上外设的是离散的数字量。**
##### 输出模式
-开漏输出(GPIO_Mode_Out_OD)
-开漏复用功能(GPIO_Mode_AF_OD)
-推挽式输出(GPIO_Mode_Out_PP)
-推挽式复用功能(GPIO_Mode_AF_PP)
###### **开漏输出**
输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)
1. 利用外部电路的驱动能力,减少IC内部的驱动。当IC内部MOSFET导通时,驱动电流是从外部的VCC流经R pull-up ,MOSFET到GND。IC内部仅需很下的栅极驱动电流。
2. 一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。(上拉电阻的阻 决定了逻辑电平转换的沿的速度 。阻 越大,速度越低功耗越小,所以负载电阻的选择要兼顾功耗和速度。)
3. OPEN-DRAIN提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。
4. 可以将多个开漏输出的Pin,连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系。这也是I2C,SMBus等总线判断总线占用状态的原理。补充:什么是“线与”?:
在一个结点(线)上, 连接一个上拉电阻到电源 VCC 或 VDD 和 n 个 NPN 或 NMOS 晶体管的集电极 C 或漏极 D, 这些晶体管的发射极 E 或源极 S 都接到地线上, 只要有一个晶体管饱和, 这个结点(线)就被拉到地线电平上. 因为这些晶体管的基极注入电流(NPN)或栅极加上高电平(NMOS),晶体管就会饱和, 所以这些基极或栅极对这个结点(线)的关系是或非 NOR 逻辑. 如果这个结点后面加一个反相器, 就是或 OR 逻辑.
其实可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为0,只有都为高电平时,与的结果才为逻辑1。
![image-20210726104938519](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726104938519.png)
###### **开漏复用功能**
可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)。端口必须配置成复用功能输出模式(推挽或开漏)
![image-20210726105034416](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726105034416.png)
###### 推挽式输出
可以输出高,低电平,连接数字器件;推挽结构一般是指两个三级管分别受到互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由IC的电源低定。
推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形方法任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小,效率高。输出即可以向负载灌电流。推拉式输出级即提高电路的负载能力,又提高开关速度
**点亮LED灯就用的推挽输出,应该是比较常用的输出方式**![image-20210726105123502](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726105123502.png)
###### **推挽式复用功能**
可以理解为GPIO口被用作第二功能时的配置情况(并非作为通用IO口使用)
![image-20210726105335453](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726105335453.png)
在STM32中选用IO模式
(1) 浮空输入_IN_FLOATING --浮空输入,可以做KEY识别,RX1
(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)
### 具体外设
#### 入门程序1 流水灯程序(标准库)
使用`正点原子ALIENTEK精英STM32F103开发板`的开发板 其实都是一样的,就是不同硬件的引脚不一样罢了
##### 原理图
![image-20210726091153077](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726091153077.png)
![image-20210726091541834](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726091541834.png)
![image-20210726091606981](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726091606981.png)
##### 程序代码 main.c
```c
//流水灯main程序
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#define LED0_GPIO_PIN GPIO_Pin_5
#define LED0_GPIO_PORT GPIOB
#define LED0_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED1_GPIO_PIN GPIO_Pin_5
#define LED1_GPIO_PORT GPIOE
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOE
// LED0 对应接口 PB5 LED1 对应接口 PE5
void myDelay(int count){
int i = 0;
for(;i<count;i++){
}
return;
}
void LED_GPIO_Config(void){
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(LED0_GPIO_CLK|LED1_GPIO_CLK, ENABLE); //使能PB,PE端口时钟
GPIO_InitStruct.GPIO_Pin = LED0_GPIO_PIN; //LED0-->PB.5 端口配置
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(LED0_GPIO_PORT, &GPIO_InitStruct); //根据设定参数初始化GPIOB.5
GPIO_InitStruct.GPIO_Pin = LED1_GPIO_PIN; //LED1-->PB.5 端口配置
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStruct); //根据设定参数初始化GPIOE.5
}
int main(void)
{
LED_GPIO_Config();
while(1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_5); //LED0对应引脚GPIOB.5拉低,亮 等同LED0=0;
GPIO_SetBits(GPIOE,GPIO_Pin_5);
myDelay(3000000); //延时一会 因为是cpu执行延时 所以不准
GPIO_SetBits(GPIOB,GPIO_Pin_5); //LED0对应引脚GPIOB.5拉高,灭 等同LED0=1;
GPIO_ResetBits(GPIOE,GPIO_Pin_5);
myDelay(3000000); //延时一会 因为是cpu执行延时 所以不准
}
}
```
#### 模拟信号转换为数字信号( hal库adc)
##### adc
###### 简介
Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将**连续变量的模拟信号转换为离散的数字信号的器件。**
典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。
###### 原理图
![](https://img-blog.csdn.net/20180424221309872)
##### 本实验原理图
![image-20210726155056030](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726155056030.png)
![image-20210726155225872](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726155225872.png)
**使用GPIO的PC1作为ADC转换的输入口,接受电位器变化的电压值。**
##### cube配置
![image-20210726155647098](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726155647098.png)
##### 程序代码 main.c
```c
#include "stdio.h"
uint16_t adc_value;
int fputc(int c, FILE *stream)
{
HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);
return 1;
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
// 中断中获取当前ad转换的值 如果ad转换完成会进入这个回调函数
//使用全局变量存储这个值
adc_value = HAL_ADC_GetValue(hadc);
}
void main(){
HAL_ADC_Start_IT(&hadc1);
while(1){
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin); //灯闪烁
printf("%d\r\n",adc_value); // 打印adc转换的数字值 范围 0-4096 因为adc转换的位数是12位 精度就是2^12 = 4096级
HAL_Delay(500);
}
}
```
##### 结果
![image-20210726160013067](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726160013067.png)
#### 数字信号转换为模拟信号(hal库dac)
##### dac
![image-20210726160501084](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726160501084.png)
##### 本次实验原理图
![image-20210726161341325](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726161341325.png)
![image-20210726160817875](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726160817875.png)
##### cubemx配置
###### 三角波
![image-20210726162630399](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726162630399.png)
![image-20210726162647821](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726162647821.png)
###### 正弦波
![cube10](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoracube10.jpg)
![cube11](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoracube11.jpg)
##### 程序代码 main.c
```c
// 三角波信号
void main(){
HAL_TIM_Base_Start(&htim6); // 开启定时器6
// 设置最高值 然后分1024级向下递减 然后上升 然后循环
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x100);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
// 任意波(示例使用正弦波,其他其实都可以 只需要换一下数组)
// 画自己的波形 正弦波 使用dma
const uint8_t sine_8bit[256] = {0x80,0x84,0x87,0x8A,0x8D,0x90,0x93,0x96,0x99,0x9D,0xA0,
0xA3,0xA6,0xA9,0xAC,0xAF,0xB1,0xB4,0xB7,0xBA,0xBD,0xC0,0xC2,0xC5,0xC8,0xCA,0xCD,0xCF,
0xD2,0xD4,0xD6,0xD9,0xDB,0xDD,0xDF,0xE1,0xE3,0xE5,0xE7,0xE9,0xEB,0xED,0xEE,0xF0,0xF1
,0xF3,0xF4,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFD,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFD,0xFD,0xFC,0xFB,0xFA,0xF9,
0xF8,0xF7,0xF6,0xF4,0xF3,0xF1,0xF0,0xEE,0xED,0xEB,0xE9,0xE7,0xE5,0xE3,0xE1,0xDF,0xDD,
0xDB,0xD9,0xD6,0xD4,0xD2,0xCF,0xCD,0xCA,0xC8,0xC5,0xC2,0xC0,0xBD,0xBA,0xB7,0xB4,0xB1,
0xAF,0xAC,0xA9,0xA6,0xA3,0xA0,0x9D,0x99,0x96,0x93,0x90,0x8D,0x8A,0x87,0x84,0x81,0x7D,
0x7A,0x77,0x74,0x71,0x6E,0x6B,0x68,0x64,0x61,0x5E,0x5B,0x58,0x55,0x52,0x50,0x4D,0x4A,
0x47,0x44,0x41,0x3F,0x3C,0x39,0x37,0x34,0x32,0x2F,0x2D,0x2B,0x28,0x26,0x24,0x22,0x20,
0x1E,0x1C,0x1A,0x18,0x16,0x14,0x13,0x11,0x10,0x0E,0x0D,0x0B,0x0A,0x09,0x08,0x07,0x06,
0x05,0x04,0x04,0x03,0x02,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x02,0x02,0x03,0x04,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0D,0x0E,0x10,0x11,0x13,
0x14,0x16,0x18,0x1A,0x1C,0x1E,0x20,0x22,0x24,0x26,0x28,0x2B,0x2D,0x2F,0x32,0x34,0x37,
0x39,0x3C,0x3F,0x41,0x44,0x47,0x4A,0x4D,0x50,0x52,0x55,0x58,0x5B,0x5E,0x61,0x64,0x68,
0x6B,0x6E,0x71,0x74,0x77,0x7A,0x7D};
void main(){
HAL_TIM_Base_Start(&htim6);
// 使用dma cpu直接读取内存中的数组 根据时钟进行输出电压值
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_8bit, 256, DAC_ALIGN_8B_R);
}
```
##### 结果
![image-20210726162714861](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726162714861.png)
![IMG_20210707_185516](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraIMG_20210707_185516.jpg)
#### 定时器(hal库timer)
##### cubemx设置
![image-20210726235414937](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726235414937.png)
##### 程序代码main.c
```c
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM1){ // 如果是timer1那么串口输出东西
// 灯闪烁 串口输出hello yhn
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
printf("hello,yhn\r\n");
}
}
```
#### 中断(hal库 interrupt)
##### 原理图
![image-20210727000345513](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727000345513.png)
##### cube配置
![image-20210727000143956](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727000143956.png)
![image-20210727000031841](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727000031841.png)
##### 程序代码main.c
```c
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ // 上升沿触发回调函数
if(GPIO_Pin == KEY0_Pin){
// 如果是key0按下 那么让灯亮
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET);
}else{
// 如果是key1按下 那么让灯熄灭
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET);
}
}
```
#### 显示屏(hal库lcd)
给液晶施加电场会改变液晶的分子排列,从而改变光线的传播方向
对角线长度 一寸= 2.54厘米
##### bmp文件
```C
BMP文件的数据按照从文件头开始的先后顺序分为四个部分:
bmp文件头(bmp file header):提供文件的格式、大小等信息
位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表
位图数据(bitmap data):就是图像数据啦^_^
```
###### 位图文件头分4部分,共**14**字节:
| **名称** | **占用空间** | **内容** | **实际数据** |
| ------------- | ------------ | ---------------------------------------------- | -------------------------------------------------------- |
| bfType | 2字节 | 标识,就是“BM”二字 | BM |
| bfSize | 4字节 | 整个BMP文件的大小 | 0x000C0036(786486)【与右键查看图片属性里面的大小值一样】 |
| bfReserved1/2 | 4字节 | 保留字,没用 | 0 |
| bfOffBits | 4字节 | 偏移数,即 位图文件头+位图信息头+调色板 的大小 | 0x36(54) |
注意,Windows的数据是倒着念的,这是PC电脑的特色。如果一段数据为50 1A 25 3C,倒着念就是3C 25 1A50,即0x3C251A50。因此,如果bfSize的数据为36 00 0C 00,实际上就成了0x000C0036,也就是0xC0036。
![img](https://img-blog.csdn.net/20140514113957562)
###### 位图信息头(BITMAPINFOHEADER )
位图信息头共**40**字节:
| **名称** | **占用空间** | **内容** | **实际数据** |
| --------------- | ------------ | ---------------------------------------------------- | ------------ |
| **biSize** | 4字节 | 位图信息头的大小,为40 | 0x28(40) |
| **biWidth** | 4字节 | 位图的宽度,单位是像素 | 0x200(512) |
| **biHeight** | 4字节 | 位图的高度,单位是像素 | 0x200(512) |
| **biPlanes** | 2字节 | 固定值1 | 1 |
| **biBitCount** | 2字节 | 每个像素的位数1-黑白图,4-16色,8-256色,24-真彩色 | 0x18(24) |
| biCompression | 4字节 | 压缩方式,BI_RGB(0)为不压缩 | 0 |
| biSizeImage | 4字节 | 位图全部像素占用的字节数,BI_RGB时可设为0 | 0x0C |
| biXPelsPerMeter | 4字节 | 水平分辨率(像素/米) | 0 |
| biYPelsPerMeter | 4字节 | 垂直分辨率(像素/米) | 0 |
| biClrUsed | 4字节 | 位图使用的颜色数如果为0,则颜色数为2的biBitCount次方 | 0 |
| biClrImportant | 4字节 | 重要的颜色数,0代表所有颜色都重要 | 0 |
**如果是彩色像素点所占的位数为16位,有可能是555 一位标识,或者是565 对应RGB**
**如果是彩色像素点所占的位数为24位 真彩色,那么RGB就是888**
因为显示屏程序非常复杂,实验中我们使用了标准库中的显示屏芯片(RA8875)的驱动程序,流程是
stm32芯片 -> 总线芯片 -> RA8875显示驱动芯片-> lcd屏幕 ,我们的程序只做到了stm32芯片到总线芯片的配置,剩下的部分纯属移植提供好的标准库显示屏驱动程序。
##### 原理图
![image-20210727001708857](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727001708857.png)
##### cubemx配置
![image-20210727001831772](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727001831772.png)
##### 程序源码
程序源码由于太过复杂 所以建议直接查看github仓库对应的基础实验的显示屏实验文件夹 ,如下地址所示
[lcd源码](https://github.com/1outOfMemory1/stm32Experiment/tree/main/src/fundamentalExperiment/8LCD)
#### sd卡(hal库)
##### 原理图
![image-20210727005535898](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727005535898.png)
##### fatfs模块
###### 1、文件系统是什么?
负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。
即在磁盘上组织文件的方法。
常用的文件系统:
-FAT / FATFS
-NTFS: 基于安全性的文件系统,是Windows NT所采用的独特的文件系统结构
-CDFS:CDFS是大部分的光盘的文件系统
-exFAT
###### 2、FATFS 文件系统
FATFS是一个完全免费开源的FAT文件系统模块,专门为小型的嵌入式系统而设计。完全用标准C语言编写,所以具有良好的硬件平台独立性。可以移植到8051、PIC、AVR、SH、Z80、H8、ARM等系列单片机上而只需做简单的修改。它支持FATl2、FATl6和FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对8位单片机和16位单片机做了优化。
FATFS是可裁剪的文件系统。
###### 3、 FATFS 文件系统特点
1)、 Windows 兼容的 FAT 文件系统(支持 FAT12/FAT16/FAT32 )
2)、 与平台无关,移植简单 。全 C 语言编写。
3)、 代码量少、效率高 。
4)、 多种配置选项
支持多卷(物理驱动器或分区,最多 10 个卷)
多个 ANSI/OEM 代码页包括 DBCS
支持长文件名、 ANSI/OEM 或 Unicode
支持 RTOS
支持多种扇区大小
只读、最小化的 API 和 I/O 缓冲区等
![img](https://pic1.zhimg.com/80/v2-2c22f5e0995a3c43d7ae733c6f11b1d8_720w.jpg)
① 底层接口, 包括存储媒介读/写接口( disk I/O )和供给文件创建修改时间的实时时钟 , 需要我们 根据平台和存储介质 编写移植代码 。
② 中间层 FATFS 模块, 实现了 FAT 文件读/写协议。 FATFS 模块提供的是 ff.c 和 ff.h 。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。
③ 最顶层是应用层, 使用者无需理会 FATFS 的内部结构和复杂的 FAT 协议,只需要调用 FATFS 模块提供给用户的一系列应用接口函数,如 f_open , f_read , f_write 和 f_close 等,就可以像在 PC 上读/写文件那样简单。
##### FATFS文件介绍
![image-20210727005852286](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727005852286.png)
diskio.c和diskio.h是硬件层。
ff.c和ff.h是FatFs的文件系统层和文件系统的API层。
FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c 。 FATFS 模块的所有配置项都是存放在 ffconf.h 里面,我们可以通过配置里面的一些选项,来满足自己的需求。 diskio.c 是硬件层,负责与底层硬件接口适配。
**因为stm32cubemx非常强大 ,只需要按照下边cubemx配置图配置之后,FATFS就已经移植成功了,所以非常节省时间。**
##### cubemx配置图
![image-20210727003110243](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727003110243.png)
![image-20210727003431187](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727003431187.png)
![image-20210727003620615](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210727003620615.png)
##### 程序代码main.c
```c
uint8_t rBuffer[512];
uint8_t wBuffer[512];
#include "stdio.h"
//重写底层函数 printf会调用fputc这个函数
int fputc(int c, FILE *stream)
{
HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);
return 1;
}
int main(void)
{
// 省略了初始化内容和一些别的东西
// 注意 下方的 retSD SDFatFS SDPath SDFile 都是hal库给你规定好的东西 直接用就行 而rBuffer 和 wBuffer是自己定义的
retSD = f_mount(&SDFatFS,SDPath,1);
printf("%d\n",retSD);
// 随便使用电脑读卡器 将sd卡格式化为FAT32 然后放入一个文件 我这里是在pic目录下存放了一个0.bin的文件 我尝试读出他的内容
retSD = f_open(&SDFile,"0:/pic/0.bin", FA_OPEN_EXISTING | FA_READ);
if(FR_OK != retSD)
while(1);
else{
unsigned int numRW;
retSD = f_read(&SDFile,rBuffer,sizeof rBuffer,&numRW);
if (retSD || numRW == 0) printf("error\n"); // error or eof 读取文件出错或者是文件结束了
else {
for(int i=0;i<512;i++){
printf("%x ",rBuffer[i]); // 串口输出
}
}
}
}
```
完整代码需要查看git log中 注释为`完成电子相框tf屏幕和sd卡fatfs的移植 均可实现基本功能`的历史版本 目录为下边github地址
[测试sd卡](https://github.com/1outOfMemory1/stm32Experiment/tree/main/src/exam100/hal_lcd_sd)
#### 串口(hal库UART)
##### 原理图
![image-20210707122949254](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210707122949254.png)
```shell
底板左侧有两个 DB-9 头的串口接口,上面的定义为 COM1,主要用于调试;下面的定义为 COM3,
可以用于用户扩展。
实际工程中,UART 不仅用于数据串行输入输出,更多的用于早期的程序调试辅助等。我们使用
标准 C lib 中的 printf()函数进行格式化的输出,输入采用中断接受的形式。
UART(Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。UART 包含 TTL 电平的
串口和 RS232 电平的串口。 TTL 电平是 3.3V 的,而 RS232 是负逻辑电平,它定义+5~+12V 为低电平,
而-12~-5V 为高电平,MDS2710、MDS SD4、EL805 等是 RS232 接口,EL806 有 TTL 接口。
串行接口按电气标准及协议来分包括 RS-232-C、RS-422、RS485 等。RS-232-C、RS-422 与 RS-485 标
准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。
我们这里用到的“串口”,指“标准串口”,也就是“RS-232 串口”。它是在 1970 年由美国电子工
业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。
它的全名是“数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”。
传统的 RS-232-C 接口标准有 22 根线,采用标准 25 芯 D 型插头座(DB25),后来使用简化为 9 芯 D 型插
座(DB9),现在应用中 25 芯插头座已很少采用。RS-232 采取不平衡传输方式,即所谓单端通讯。由于
其发送电平与接收电平的差仅为 2V 至 3V 左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其
传送距离最大为约 15 米,最高速率为 20kb/s。RS-232 是为点对点(即只用一对收、发设备)通讯而设计
的,其驱动器负载为 3~7kΩ。所以 RS-232 适合本地设备之间的通信。
```
##### cubemx配置
![image-20210726235842982](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726235842982.png)
##### 程序代码main.c
```c
#include "stdio.h"
//重写底层函数 printf会调用fputc这个函数
int fputc(int c, FILE *stream)
{
HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);
return 1;
}
//while 死循环
void main(){
while (1){
//每隔0,。5秒不停输出hello,yhn
printf("hello,yhn\r\n");
HAL_Delay(500);
}
}
```
#### 存储器(EEPROM)
##### 不同存储器的区别
RAM是静态随机存取存储器。它是一种具有静止存取功能的内存,不需要刷新电路即能保存它内部存储的数据。STM32F1系列可以通过FSMC外设来拓展SRAM。
注意:SRAM和SDRAM是不相同的,SDRAM是同步动态随机存储器,同步是指内存工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据读写。STM32的F1系列是不支持SDRAM的。
EEPROM 指的是“电可擦除可编程只读存储器”,即Electrically Erasable Programmable Read-Only Memory。它的最大优点是可直接用电信号擦除,也可用电信号写入。EEPROM不能取代RAM的原应是其工艺复杂, 耗费的门电路过多,且重编程时间比较长,同时其有效重编程次数也比较低。
Flash memory 指的是“闪存”,所谓“闪存”,它也是一种非易失性的内存,属于EEPROM的改进产品。它的最大特点是必须按块(Block)擦除(每个区块的大小不 定,不同厂家的产品有不同的规格), 而EEPROM则可以一次只擦除一个字节(Byte)。目前“闪存”被广泛用在PC机的主板上,用来保存BIOS程序,便于进行程序的升级。其另外一大应 用领域是用来作为硬盘的替代品,具有抗震、速度快、无噪声、耗电低的优点,但是将其用来取代RAM就显得不合适,因为RAM需要能够按字节改写,而 Flash ROM做不到。
##### iic总线
###### 简介
IIC(Inter Integrated Circuit,集成电路总线)是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由**数据线** **SDA** 和**时钟** **SCL** 构成的串行总线,可发送和接收数据。在 CPU (单片机)与IIC模块之间、IIC模块与IIC模块之间进行双向传送。
IIC的一些特点:
- **IIC是半双工,而不是全双工**
- **IIC是真正的多主机总线**,(对比SPI在每次通信前都需要把主机定死,而IIC可以在通讯过程中,改变主机),如果两个或更多的主机同时请求总线,可以通过冲突检测和仲裁防止总线数据被破坏
- **起始和终止信号都是由主机发出的**,连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号
- 在**起始信号后**必须发送一个**7位从机地址+1位方向位**,用“0”表示主机发送数据**,**“1”表示主机接收数据。
- 每当主机向从机发送完一个字节的数据,**主机总是需要等待从机给出一个应答信号**,以确认从机是否成功接收到了数据
- **起始信号是必需的**,结束信号和应答信号,都可以不要
**注**:实际使用中,**一般是单片机作为主机,其它器件作为从机**,单片机先向器件发送信息表示要读取数据,之后转变传输方向,器件发送数据到单片机。
###### IIC物理连接
使用IIC通信的IIC器件有很多,比如陀螺仪加速度计MPU6050,**EEPROM存储芯片AT24C02(本次实验就是用的这个)**等,通过IIC总线,可以与单片机之间进行数据传输。
- IIC通信线只有只有两根,数据线SDA的高低电平**传输2进制的数据**,时钟线SCL通过方波信号**提供时钟节拍**
- 多个IIC器件可以并联在IIC总线上,每个器件有特定的地址,分时共享IIC总线
- 实际使用IIC当然还要连接电源以及共地哦
![image-20210726145632870](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726145632870.png)
###### IIC起始结束信号
![image-20210726145831558](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726145831558.png)
- 起始:时钟线SCL为高时,数据线SDA由高到低
- 停止:时钟线SCL为高时,数据线SDA由低到高
注:SDA和SCL同时为高时,为IIC总线的空闲状态
###### IIC应答信号
![image-20210726145943233](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726145943233.png)
这表示IIC的应答机制
- 下面的波形:SCL,主机产生的时钟脉冲
- 上面的波形:SDA,主机发送的8位数据
- 中间的波形:SDA,从机在第9个时钟信号进行**拉低回应**,表示收到了主机发来的数据,**拉高则表示不应答**
注:实际上,上面和中间是同样的SDA线,这里只是分开示意。因为IIC应答是一种相互关系,**单片机发数据给IIC器件,IIC器件要进行应答,表示收到了数据,同样,单片机接收IIC器件的数据后,也要给IIC器件一个应答**。
既然发送完都需要对方回应,那什么时候使用不应答呢?就是在读取到本次数据后,如果不需要继续读取,则发送非应答,对方以为你没收到这次数据,则就不会继续发送了。
###### IIC完整传输时序
![image-20210726150314017](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726150314017.png)
- 开始标志(S)发出后,主设备会传送一个7 位的Slave 地址,并且后面跟着一个第8位,称为Read/Write 位。
- R/W 位表示主设备是在接受从设备的数据还是在向其写数据。
- 然后,主设备释放SDA 线,等待从设备的应答信号(ACK)。每个字节的传输都要跟随有一个应答位。
- 应答产生时,从设备将SDA 线拉低并且在SCL 为高电平时保持低。
- 数据传输以停止标志(P)结束,然后释放总线。但主设备也可以产生重复的开始信号去操作另一台从设备,而不发出结束标志。
- **所有的SDA 信号变化都要在SCL 时钟为低电平时进行,除了开始和结束标志**
###### 常用的数据收发方式(时序)
上面1.3小节是IIC的基础时序,在实际使用中,一般是对某个IIC器件的某个寄存器进行读写操作,因此,对于寄存器的读写操作,还要遵循下面的组合时序逻辑。
1. 写一个字节
用于对IIC器件某个寄存器的配置,如对MPU6050的某些参数进行设置。
![img](https://pic1.zhimg.com/80/v2-44b4ecd82e3e2fff3b66083eb664694c_720w.jpg)
- 写寄存器时,主设备除了发出开始标志和地址位,还要加一个R/W 位,0 为写,1 为读
- 在第9 个时钟周期(高电平时),MPU6050 产生应答信号
- 主设备开始传送寄存器地址,并接到应答
- 然后开始传送寄存器数据,仍然要有应答信号
- 最后主设备发送停止信号。
2. 连续写多个字节
对连续地址的写入,这个用的较少。
![img](https://pic2.zhimg.com/80/v2-ebdbee6f1dd3b4e48588af0361b7eaad_720w.png)
通信时序与上面的“写一个字节”类似,上面是写一个字节后就停止了,若要连续写,则继续写即可,只要可以收到从机Ack。
3. 读一个字节
用于读取IIC器件某个寄存器的数值。
![img](https://pic1.zhimg.com/80/v2-bf93635d2f6db43bffc6a1eead1ce008_720w.png)
- 首先由主设备产生开始信号,然后发送从设备地址位和一个写数据位,等待应答
- 然后发送寄存器地址,才能开始读寄存器
- 收到应答信号后,主设备再发一个开始信号,然后发送从设备地址位和一个读数据位
- 然后,作为从设备的MPU6050 产生应答信号并开始发送寄存器中的数据
- 通信以主设备产生的拒绝应答信号(nACK)和结束标志(Stop)结束
- 拒绝应答信号(nACK)产生定义为SDA 数据在第9 个时钟周期一直为高
4. 连续读多个字节
也是用于读取IIC器件某个寄存器的数值,当某些数据一位字节不够表示,或有一组连续的数据需要读时,可以使用该模式。
![img](https://pic1.zhimg.com/80/v2-ac0a041f53b8900bc6d5fcd9de5c7818_720w.png)
通信时序与上面的“读一个字节”类似,上面是读一个字节后就nAck叫停,若要连续写,则发送Ack,直到不需要继续读时再回复nAck。
##### 本次实验原理图
通过原理图可以看出存储器和iic总线相连接。所以直接使用iic总线即可。
![image-20210726144846856](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726144846856.png)
![image-20210707142245078](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210707142245078.png)
![image-20210707144258216](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210707144258216.png)
##### EEPROM地址的确认
如上图1原理图所示,而且实验中使用的型号是AT24C02 所以高字节使用1010 ,根据读还是写来确定地址
```shell
x x x x A2 A1 A0 WriteBit
1 0 1 0 0 0 1 0 0xA2
1 0 1 0 0 0 1 3 0xA3
```
##### cubemx配置
![image-20210726145118797](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typoraimage-20210726145118797.png)
##### 程序代码 main.c
```c
#include "stdio.h"
#define ADDR_24LCxx_Write 0xA3 // 1010 0010 xxxx A2 A1 A0 WriteBit
#define ADDR_24LCxx_Read 0xA3 // 1010 0011 xxxx A2 A1 A0 ReadBit 测试后 不管是A3 还是A2都可以 不知道为什么
#define BufferSize 256
uint8_t WriteBuffer[BufferSize];
uint8_t ReadBuffer[BufferSize];
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
void main(){
/*
HAL_I2C_Mem_Write 参数
I2C_HandleTypeDef *hi2c 选择是哪个i2c
uint16_t DevAddress 器件地址
uint16_t MemAddress 寄存器地址
uint16_t MemAddSize 写入单个数据字节长度
uint8_t *pData 写入数据地址
uint16_t Size 写入数据长度
uint32_t Timeout 超时报错时间
*/
for(i=0; i<256; i++)
WriteBuffer[i]=i; // 循环
for (int j=0; j<32; j++)
{
// 循环向eeprom写入 1 2 3 ... 的数据
if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 8*j, I2C_MEMADD_SIZE_8BIT,WriteBuffer+8*j,8, 1000) == HAL_OK)
{
printf("\r\n EEPROM 24C02 Write Test OK \r\n");
HAL_Delay(20);
}
else
{
HAL_Delay(20);
printf("\r\n EEPROM 24C02 Write Test False \r\n");
}
}
// 从eeprom读出刚写入的数据
HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff);
//从串口打印出来
for(i=0; i<256; i++)
printf("0x%02X ",ReadBuffer[i]);
}
```
##### 结果
![串口正序1](https://tuchuang1234.oss-cn-shenzhen.aliyuncs.com/typora串口正序1.jpg)
#### 延时程序
我们可以使用 Systick 定时器或者 WatchDog 定时器
实现系统精确延时,STM32 使用的是 Systick、MK64 使用的是 WatchDog。
```c
/*
**************************************************************************************
* 函 数 名: BSP_Delay_us
* 功能说明: 板载 Delay 微秒功能函数
* 形 参: nus : 要延时的 us 数
* 返 回 值: 无
**************************************************************************************
*/
void BSP_Delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16)));
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
/*
**************************************************************************************
* 函 数 名: BSP_Delay_ms
* 功能说明: 板载 Delay 毫秒功能函数
* 形 参: nms : 要延时的 ms 数,nms<=1864
* 返 回 值: 无
**************************************************************************************
*/
//延时 nms
//注意 nms 的范围
//SysTick->LOAD 为 24 位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK 单位为 Hz,nms 单位为 ms
//对 72M 条件下,nms<=1864
void BSP_Delay_ms(uint16_t nms)
{
uint32_t temp;
SysTick->LOAD=(uint32_t)nms*fac_ms; //时间加载(SysTick->LOAD 为 24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
```
stm32学习