博主之前文章介绍的STC51 系列单片机是一款结构简单、易于学习的嵌入式微控制器,但是由于标准
8051 架构诞生于 70
年代,其硬件架构、资源数量以及编程方式都已显老旧,成本和性能方面也皆落后于其它架构产品,市场占有率逐步遭到侵蚀,目前仅常见于一些教学与发烧友使用的范畴。伴随近几年物联网行业的快速兴起,STM32 等基于
ARM Cortex
内核的微控制器,凭借丰富的片上资源与简单易用的标准外设库,逐步成为消费与工业领域中的主流产品。
意法半导体 成立于 1987 年,由意大利
S GS 和法国 T homson
两家半导体企业合并而成,本文所介绍的STM32F103C8T6 属于该公司应用极为广泛的型号,其提供的STM32F10x Standard Peripheral Library
标准外设库对
STM32 片上资源进行了完善的封装,相对于 ST
公司目前力推的HAL/LL 库,标准外设库更加接近于传统的寄存器操作,因而也较为容易向兆易创新 的GD32 等国产微控制器移植。
片上资源概览
STM32F103C8T6 基于 ARM 32 位 Cortex™-M3
内核,使用2.0V ~ 3.6V
电压供电,工作频率最高可以达到72MHz
,内部采用64K
或128K
字节
Flash 程序存储器,以及高达20K
字节的 SRAM 数据存储器;内置
CRC 循环冗余校验以及 96 位编码(即 24
位的十六进制数)的芯片唯一序列号(例如:52FF69067871515237582567
)。
其主系统主要由 4 个控制单元 (DCode
总线D-bus 、系统总线S-bus 、通用DMA1 、通用DMA2 )以及
4
个受控单元 (内部SRAM 、内部Flash 、FSMC 、AHB
到 APB 的桥AHB2APBx )组成,它们通过一个多级的 AHB
总线相互进行连接,如下图所示:
ICode 总线:连接 M3 内核指令总线与 Flash
的指令接口,用于进行指令预取。
DCode 总线:连接 M3 内核 DCode 总线与 Flash
的数据接口,用于完成常量加载和调试访问。
System 总线:连接 M3 内核系统总线与总线矩阵 Bus
Matrix,用于协调内核与 DMA 之间的通信。
DMA 总线:连接 DMA 的 AHB 主接口与总线矩阵 Bus
Matrix,用于协调 DCode 和 DMA 对 SRAM、Flash 以及其它外设的访问。
Bus Matrix 总线矩阵:利用轮换算法管理内核系统总线与
DMA 主总线之间访问的仲裁,由 4 个控制单元(DCode、系统总线、DMA1 和 DMA2
总线)以及 4 个受控单元(FLITF、SRAM、FSMC、AHB2APB 桥)组成。
AHB/APB bridges :两个 AHB/APB 桥提供了 AHB 与 2 条
APB
总线之间的同步连接,APB1 操作速度低于36MHz
,APB2 操作速度最高可达72MHz
。每次复位以后,除
SRAM 和 FLITF
以外的外设都会被关闭。使用外设之前,必须通过设置寄存器RCC_AHBENR 打开该外设的时钟。
AHB(高级高性能总线,Advanced High performance
Bus) 是一种系统总线,主要用于连接 CPU、DMA、DSP
等高性能模块,由主模块、从模块、基础结构三部分组成,数据传输总是由主模块发起从模块回应。APB(高级外围总线,Advanced
Peripheral Bus) 是一种外围总线,主要用于 UART
等低带宽外设之间的连接,其唯一的主模块是 APB 桥;两者都遵循 ARM
公司推出的AMBA 芯片总线规范。
时钟系统
由于 STM32 外设资源众多,工作的时钟频率各不相同,所以采用了多达 5
个时钟源:片上经过出厂调校的8MHz
RC
振荡器系统时钟HSI ,以及带校准的40kHz
RC
振荡器作为实时时钟LSI ,也可以采用外置4 ~ 16MHz
晶体振荡器作为系统时钟HSE ,以及带校准功能的32kHz
RTC 振荡器作为实时时钟LSE ;最后还内置了用于对 CPU
时钟进行倍频的的PLL 锁相环。
HSE ,High-Speed External
Clock Signal
外部高速晶体振荡器
4 ~ 16MHz
4 ~ 16MHz
晶振
-
系统时钟/RTC
成本高,温漂小
LSE ,Low-Speed External
Clock Signal
外部低速晶体振荡器
32.768kHz
32.768kHz
晶振
带校准
RTC
成本高,温漂小
HSI ,High-Speed Internal
Clock Signal
内部高速 RC 振荡器
8MHz
无
出厂调校
系统时钟
成本低,温漂大
LSI ,Low-Speed Internal
Clock Signal
内部低速 RC 振荡器
40kHz
无
带校准
RTC
成本低,温漂大
PLL ,Phase Locked
Loop
锁相环倍频输出
2~16
倍,小于72MHz
HSI÷2
、HSE
、HSE÷2
-
RTC
成本低,温漂大
注意 :HSI 、HSE 、PLL 属于高速时钟源,LSI 、LSE 属于低速时钟源,任何时钟源都可以根据需要,独立进行启动或者关闭,从而优化芯片功耗。
下面是外部高速时钟源HSE 的交流时序图,注意图中Tʜsᴇ
标识的部分为一个系统时钟周期。
下图当中,当HSI 作为PLL 时钟的输入时,最高系统时钟频率只能达到64MHz
。当使用
USB 功能时,必须同时使用HSE 和PLL ,并且
CPU
的频率必须为48MHz
或72MHz
。当需要的ADC 采样时间为1μs
的时候,**APB2**必须设置为14MHz
、28MHz
或56MHz
。
GPIO
STM32F103C8T6 采用 LQFP48 方式封装,一共拥有 37 个
I/O 引脚,被分为PA (15 个)、PB (15
个)、PC (3 个)、PD (2
个)、PE (0 个)五个组,所有 I/O 接口可以映像到 16
个外部中断,并且大部份端口都可以可以兼容5V
信号。每个 I/O
端口可以接受或输出8mA
电流,灌电流则可达到20mA
,下面是详细的引脚定义图:
每个 GPIO 端口都拥有两个 32
位配置寄存器GPIOx_CRL
和GPIOx_CRH
,两个 32
位数据寄存器GPIOx_IDR
和GPIOx_ODR
,一个 32
位置位/复位寄存器GPIOx_BSRR
和一个 16
位复位寄存器GPIOx_BRR
和一个 32
位锁定寄存器GPIOx_LCKR
。
GPIO
端口的每个位都可以通过软件将其配置为输出 (推挽输出GPIO_Mode_Out_PP
、开漏输出GPIO_Mode_Out_OD
)、输入 (浮空输入GPIO_Mode_IN_FLOATING
、上拉输入GPIO_Mode_IPU
、下拉输入GPIO_Mode_IPD
、模拟输入GPIO_Mode_AIN
)、复用 (复用推挽输出GPIO_Mode_AF_PP
、复用开漏输出GPIO_Mode_AF_OD
)功能。
除具有模拟输入功能的引脚之外,所有 GPIO
都拥有大电流通过能力。必要时可以对 GPIO 进行锁定,以避免意外擦写 GPIO
相关的寄存器。位于APB2 上的 GPIO
引脚,其脉冲转换速度可达18MHz
。
定时器
STM32F103C8T6 拥有 7 个定时器,其中 1
个用于电机控制的 16 位 PWM 高级控制定时器、3 个 16 位通用定时器、2
个看门狗定时器(包含独立型的和窗口型)、1 个 24
位自减型系统嘀嗒定时器。
高级控制定时器
TIM1 :TIM1 可以被视为分配到 6 个通道的三相 PWM
发生器,具有带死区插入的互补 PWM
输出,还可以用作完整的通用定时器;其四个独立通道可分别用于:输入捕获 、输出比较 、产生边缘或中心对齐模式的PWM 、单脉冲输出 。当配置为
16
位普通定时器时,与TIM2 、TIM3 、TIM4 具有相同功能;配置为
16 位 PWM 发生器时,具有0 ~ 100%
的全调制能力。
通用定时器 TIM2、TIM3、TIM4 :STM32F103C8T6 内置有 3
个可同步运行的标准定时器,每个定时器都拥有一个 16
位自动加载递加/递减计数器、一个 16 位预分频器、4
个独立通道,每个通道都可用于输入捕获 、输出比较 、PWM 、单脉冲输出 ,它们还可以通过定时器链接功能与高级控制定时器
TIM1 协同工作,从而提供同步或事件链接功能。
独立看门狗定时器
IWDG :用于发生问题时复位整个系统,或作为一个自由定时器为应用程序提供超时管理;内部基于
12 位递减计数器和 8 位预分频器,并由内置40kHz
的 RC
振荡器提供时钟,由于该 RC
振荡器独立于主时钟,因此可以运行在停机和待机模式。可通过程序配置为软件或者硬件启动的看门狗。
窗口看门狗定时器
WWDG :用于在发生问题时复位整个系统,它由主时钟驱动,具有早期预警中断功能;其内置有
7 位的递减计数器,并且可以设置为自由运行。
系统嘀嗒定时器
SysTick :仅用于实时操作系统,也可作为一个标准的递减计数器,具有
24 位的递减计数器、自动重加载功能、当计数器为 0
时能产生一个可屏蔽系统中断、可编程时钟源等特性。
通信接口
STM32F103C8T6 拥有 2 个 I²C
接口、3 个 USART 接口、2 个 SPI
接口、1 个 CAN 接口、1 个 USB 2.0
全速接口。
I²C :内置 I²C
总线接口能够工作于多主模式或从模式,支持标准和快速模式;I²C 接口支持 7
位或 10 位寻址,7 位从模式时支持双从地址寻址,并且内置了硬件 CRC
发生器/校验器,支持使用 DMA 操作并支持 SMBus 总线 2.0 版/PMBus 总线。I²C
总线的连接线一般不超过 2
米,并且理论上数据线需要增加2KΩ
上拉电阻,所有与STM32F103C8T6 连接的设备都需要共同接地。
USART :片上的 USART 接口具有硬件 CTS 和 RTS
信号管理、支持 IrDA SIR ENDEC 传输编解码、兼容 ISO7816 的智能卡并提供
LIN 主/从功能;其中,USART1
接口通信速率可达4.5 Mbit/S
,而其它 USART 接口可达
2.25 Mbit/S
;所有 USART 接口都可以使用 DMA 操作。
SPI :STM32F103C8T6 拥有 2 个 SPI
接口,主、从模式下全双工和半双工通信速率可达18 Mbit/S
。3
位预分频器可以产生 8 种主模式频率,并且每帧可配置为 8 位或 16 位,所有
SPI 接口依然可以使用 DMA 操作。
CAN :同时兼容 CAN 2.0A 和 2.0B
规范,位速率高达1Mbit/S
,可以接收和发送 11
位标识符的标准帧,也可以收发 29 位标识符的扩展帧。
USB 2.0 :内嵌 1 个全速 USB
控制器(12Mbit/S),具有待机/唤醒功能,其专用48MHz
时钟由内部主锁相环PLL 直接产生(时钟源必须为HSE 晶体振荡器)。
51 架构单片机内置的 UART 是通用异步收发器,没有同步时钟线;而 STM32
中的 USART
是通用同步/异步收发器,带有同步时钟线USART_CK ;由于异步模式更加常用,而同步模式使用频率较少,所以二者区别不大。
DMA
DMA (直接内存存取,Direct Memory
Access)用来提供在外设与存储器或者存储器与存储器之间的高速数据传输,传输过程无需经过
CPU 进行干预,数据直接通过 DMA 快速进行操作,从而节省大量 CPU 资源。
STM32F103 拥有 2 个 DMA 控制器共 12
个通道,其中DMA1 拥有 7
个通道,DMA2 拥有 5
个通道,每个通道都用来管理外部设备对片内存储器的访问请求,此外还有一个仲裁器来协调各个
DMA
请求的优先级。除了管理外部设备(Timer、ADC、SPI、I²C、USART)到储存器之间数据的双向传输,DMA
还能够管理存储器之间的数据传输。
STM32F103 最小系统
STM32F103C8T6 的最小系统电路由电源电路 、复位电路 、时钟电路 、程序下载电路 4
部份组成,具体请参考下面的电路图:
电源电路 :STM32F103C8T6 拥有三路逻辑电源VDD_1 、VDD_2 、VDD_3 和一路模拟电源VDDA (由于当前实验电路对于模拟电压的读取精度无特殊要求,所以模拟电源与逻辑电源可以共用)。此外,VBAT 引脚上还连接了一枚3V
纽扣电池BT1 ,用于为内部的
RTC
时钟供电。而后续串接的去耦电容C1 、C3 、C3 、C4 则主要用于稳定电源以及滤除杂波。STM32F103C8T6 使用的3V
电压是通过AMS1117-3V 稳压芯片获得,该芯片将计算机
Micro USB
接口的5V
供电电压转换为3.3V
电压,然后将这个3.3V
电压连接至继电器J1 的开关控制位。
注意 :上面电路原理图当中的VCC 是指C = Circuit
,表示接入电路的电压;VDD 是指D = Device
,表示元件内部的工作电压;VSS 是指S = Series
,表示公共连接,通常指公共接地端。
复位电路 :STM32F103C8T6 内部已经拥有一个上电复位电路,但是生产环境下为了防止复位引脚悬空,通常还是会连接一组由电容C9 电阻R7 共同组成的
RC
外部复位电路,而手动外部复位则是通过后续连接的微动开关K1 (即实验电路里的复位按键)来完成。
时钟电路 :频率为8MHz
的晶振TX1 是外部系统时钟,由C5 和C6 两枚电容协助起震;频率为32.768KHz
的晶振TX2 用于外部RTC 实时时钟电路,由C7 和C8 两枚电容协助起震。
程序下载电路 :芯片STC15W201S 用于切换单片机启动模式,实现
ASP 程序自动下载;STM32F103C8T6 使用 USART
串口为单片机下载程序,实验电路中 USB 转 TTL
电平模块的TXD 和RXD 引脚分别连接至单片机的PA10/USART1_RX 和PA9/USART1_TX ,GND 引脚与单片机的VSS_1 、VSS_2 、VSS_3 、VSSA 共同接地。
STM32F103C8T6 通过BOOT0 与BOOT1 两个引脚的电平状态组合,选择何时接收串口传送过来的程序以及何时运行这些程序,即设置微控制器的启动方式。当按键K1 处于弹起状态时,将会拉低STC15W201S 的ASPK 引脚的电平状态,而STC15W201S 的ASPL 引脚连接了一枚
LED 状态指示灯并接入
GND,通过STC15W201S 单片机控制STM32F103C8T6 的BOOT0 、BOOT1 引脚电平状态,进而实现程序的自动下载。
Flash ISP
任意电平
0
从闪存启动,即从 Flash
开始执行用户程序。
Bootloader
0
1
擦写 Bootloader 接收串口传送的程序
RAM ISP
1
1
从 SRAM
启动,下载速度较快,主要用于调试阶段。
ISP 在线系统编程(In-System
Programming)是一种无需将程序存储芯片从嵌入式设备上取出就能对其进行编程与程序下载的方法。
Keil MDK-ARM 设置
虽然当前 ST 公司正在力推STM32Cube(HAL &
LL) 固件库,但是出于使用习惯,这里依然选择了STM32
Standard Peripheral
Libraries 标准固件库来编写实验代码,新建一个Merkava
文件夹,并添加CMSIS 、Lib 、Startup 、User 四个子文件夹,具体目录结构如下所示:
1 2 3 4 5 6 7 Merkava: ├─CMSIS ├─Lib │ ├─inc │ └─src ├─Startup └─User
将STM32F10x_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
目录下的stm32f10x.h
、system_stm32f10x.c
、system_stm32f10x.h
三个文件,以及STM32F10x_V3.5.0\Libraries\CMSIS\CM3\CoreSupport
目录下的所有文件复制到刚才新建的CMSIS 目录下,完成后目录结构如下:
1 2 3 D:\Workspace \merkava \CMSIS (master -> origin ) λ ls core_cm3.c core_cm3.h stm32f10x.h system_stm32f10x.c system_stm32f10x.h
然后进入STM32F10x_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm
目录将所有文件复制到刚才新建的Startup 目录:
1 2 3 4 D:\Workspace \merkava \Startup (master -> origin ) λ ls startup_stm32f10x_cl.s startup_stm32f10x_hd_vl.s startup_stm32f10x_ld_vl.s startup_stm32f10x_md_vl.s startup_stm32f10x_hd.s startup_stm32f10x_ld.s startup_stm32f10x_md.s startup_stm32f10x_xl.s
接下来再将STM32F10x_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver
目录下的src
和inc
两个子文件夹拷贝到刚才新建的Lib 目录:
1 2 3 D:\Workspace \merkava \Lib (master -> origin ) λ ls inc / src /
最后将STM32F10x_V3.5.0\Project\STM32F10x_StdPeriph_Template
目录下的main.c
、stm32f10x_conf.h
、stm32f10x_it.c
、stm32f10x_it.h
四个文件拷贝至刚才新建的User 目录:
1 2 3 D:\Workspace \merkava \User (master -> origin ) λ ls main.c stm32f10x_conf.h stm32f10x_it.c stm32f10x_it.h
完成 STM32 官方标准库文件的拷贝与复制之后,还需要对 Keil uVision
进行相应设置。首先新建一个【New uVision Project】,由于 Keil uVision
5.28.0.0 并未内置 STM32F103C8T6 支持包,因此需要点击菜单栏上的【Pack
Installer】功能手动进行安装。当建立好 uVision 项目之后,再点击【Manage
Project Items】图标将刚才建立的代码目录添加至 Keil uVision 项目。
将CMSIS
目录下的 2 个.c
文件全部添加至 Keil
uVision
项目的CMSIS 组,然后将Lib\src
目录下的所有文件添加至Lib 组,将Startup
目录下的startup_stm32f10x_md.s
汇编文件添加至Startup 组,最后将User
下的
2 个.c
文件添加至User 组,最后保存退出。
接下来进一步对 Keil uVision
进行一些初始化的设置,这里首先设置【Target】选项卡下面的外部晶振频率为8.0 MHz
,然后勾选【Output】选项卡下的Create HEX File
。
然后在【C/C++】选项卡下的【Preprocessor
Symbols】内的【Define】输入框内填入USE_STDPERIPH_DRIVER,STM32F10X_MD
。
最后点击【Include
Paths】左侧的添加按钮,把刚才新建的那些文件夹逐一选入。
官方库文件添加完毕之后,还需要新建Basic
和Hardware
两个目录来放置开发人员编写的代码,同样按照以上步骤将其添加至
Keil uVision 开发环境当中,至此项目中所有目录的功能说明如下:
CMSIS :内核驱动程序;
Lib :内部功能基本函数库;
Startup :汇编编写的启动程序;
User :用户主函数以及其它用户程序;
Basic :内部功能驱动函数;
Hardware :外部硬件驱动函数。
LED 发光二极管
LED 闪烁
delay_us()
:微秒级延时函数;
delay_ms()
:毫秒级延时函数;
delay_s()
:秒级延时函数;
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include "delay.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { RCC_Configuration(); LED_Init(); while (1 ) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); delay_us(50000 ); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0 )); delay_us(50000 ); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 - GPIO_ReadOutputDataBit(LEDPORT, LED1))); delay_ms(500 ); GPIO_SetBits(LEDPORT, LED1); delay_s(1 ); GPIO_ResetBits(LEDPORT, LED1); delay_s(1 ); GPIO_Write(LEDPORT, 0x0001 ); delay_s(2 ); GPIO_Write(LEDPORT, 0x0000 ); delay_s(2 ); } }
led.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef __LED_H #define __LED_H #include "sys.h" #define LEDPORT GPIOB #define LED1 GPIO_Pin_0 #define LED2 GPIO_Pin_1 void LED_Init (void ) ; #endif
led.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include "led.h" void LED_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = LED1 | LED2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LEDPORT, &GPIO_InitStructure); }
delay.h
1 2 3 4 5 6 7 8 9 10 #ifndef __DELAY_H #define __DELAY_H #include "sys.h" void delay_s (u16 s) ;void delay_ms (u16 ms) ;void delay_us (u32 us) ;#endif
delay.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include "delay.h" #define AHB_INPUT 72 void delay_us (u32 uS) { SysTick->LOAD = AHB_INPUT * uS; SysTick->VAL = 0x00 ; SysTick->CTRL = 0x00000005 ; while (!(SysTick->CTRL & 0x00010000 )); SysTick->CTRL = 0x00000004 ; } void delay_ms (u16 ms) { while (ms-- != 0 ) { delay_us(1000 ); } } void delay_s (u16 s) { while (s-- != 0 ) { delay_ms(1000 ); } }
LED 呼吸灯
uX
类型变量存放在 SRAM 当中,程序中可以任意进行修改。
u8
:8 位无符号变量;
u16
:16 位无符号变量;
u32
:32 位无符号变量;
vuX
类型变量主要用于中断处理函数。
vu8
:易变的 8 位无符号变量;
vu16
:易变的 16 位无符号变量;
vu32
:易变的 32 位无符号变量;
ucX
类型变量存放在 Flash 当中,程序里只能读不能写。
uc8
:只读的 8 位无符号变量;
uc16
:只读的 16 位无符号变量;
uc32
:只读的 32 位无符号变量;
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include "delay.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 MENU; u16 t, i; RCC_Configuration(); LED_Init(); MENU = 0 ; t = 1 ; while (1 ) { if (MENU == 0 ) { for (i = 0 ; i < 10 ; i++) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); delay_us(t); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0 )); delay_us(501 - t); } t++; if (t == 500 ) { MENU = 1 ; } } if (MENU == 1 ) { for (i = 0 ; i < 10 ; i++) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); delay_us(t); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0 )); delay_us(501 - t); } t--; if (t == 1 ) { MENU = 0 ; } } } }
LED 按键
GPIO
通常为高电平状态,当按键K2 和K3 按下时,PA0 和PA1 将分别被下拉为低电平状态。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include "delay.h" #include "key.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 a; RCC_Configuration(); LED_Init(); KEY_Init(); while (1 ) { if (GPIO_ReadInputDataBit(KEYPORT, KEY1)) { GPIO_ResetBits(LEDPORT, LED1); } else { GPIO_SetBits(LEDPORT, LED1); } GPIO_WriteBit(LEDPORT, LED1, (BitAction)(!GPIO_ReadInputDataBit(KEYPORT, KEY1))); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 - GPIO_ReadOutputDataBit(LEDPORT, LED1))); while (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) ; } } if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { a++; if (a > 3 ) { a = 0 ; } GPIO_Write(LEDPORT, a); while (!GPIO_ReadInputDataBit(KEYPORT, KEY1)); } } } }
key.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __KEY_H #define __KEY_H #include "sys.h" #define KEYPORT GPIOA #define KEY1 GPIO_Pin_0 #define KEY2 GPIO_Pin_1 void KEY_Init (void ) ; #endif
key.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "key.h" void KEY_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(KEYPORT, &GPIO_InitStructure); }
Flash 闪存
使用 Flash 存储器保存上面 LED 按键实验当中示例 4
的按键状态,需要向Lib 目录下添加stm32f10x_flash.c
库文件,操作
Flash 存储器时有以下注意事项:
必须严格遵循先擦后写 的操作顺序;
Flash 每页拥有 1024 个地址,但整个 Flash
的起始地址为0x08000000
;
Flash 的擦除操作必须以页 为单位,写入时必须以 16
位宽度为单位,允许跨页进行写入;
进行 Flash 擦写操作时,必须打开内/外部的高速晶振;
由于 Flash 存储器可以擦写 10
万次左右,所以不能进行死循环擦写数据,造成该页的损坏;
擦写 Flash
时需要避开已经使用了的用户程序存储区,否则错误的擦除用户程序导致错误;
Flash
每擦除一页(1k
大小)需要耗费10ms
,操作起来速度较慢,并且不能进行单个字节的擦写。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include "delay.h" #include "flash.h" #include "key.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" #define FLASH_START_ADDR 0x0801f000 int main (void ) { u16 a; RCC_Configuration(); LED_Init(); KEY_Init(); a = FLASH_R(FLASH_START_ADDR); GPIO_Write(LEDPORT, a); while (1 ) { if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { a++; if (a > 3 ) { a = 0 ; } GPIO_Write(LEDPORT, a); FLASH_W(FLASH_START_ADDR, a); while (!GPIO_ReadInputDataBit(KEYPORT, KEY1)); } } } }
flash.h
1 2 3 4 5 6 7 8 9 #ifndef __FLASH_H #define __FLASH_H #include "sys.h" void FLASH_W (u32 add, u16 dat) ;u16 FLASH_R (u32 add) ; #endif
flash.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "flash.h" void FLASH_W (u32 add, u16 dat) { FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); FLASH_ErasePage(add); FLASH_ProgramHalfWord(add, dat); FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); FLASH_Lock(); } u16 FLASH_R (u32 add) { u16 a; a = *(u16 *)(add); return a; }
注意 :如果需要使用 Flash
存储临时数据,需要考虑到当前单片机程序所占用的空间大小,然后在用户程序没有占用的空白区域里,相对靠后的位置放置临时的自定义数据。
Buzzer 无源蜂鸣器
蜂鸣器BP1 一端通过限流电阻R1 连接到
3V 电源,另外一端通过 PNP
三极管VT1 进行控制,该三极管的集电极连接至
GND,基极通过限流电阻R3 连接至 STM32
的PB5 引脚;当PB5 输出高电平时,三极管的集电极C 端与发射极E 端断开,蜂鸣器处于断开状态;当PB5 输出低电平时,三极管的集电极C 端与发射极E 端导通,蜂鸣器开始上电工作。如果要发出1KHz
频率的声音,需要
1 秒需要经过 1000 个周期,即1000us
。
按键控制蜂鸣器
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include "buzzer.h" #include "delay.h" #include "flash.h" #include "key.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" #define FLASH_START_ADDR 0x0801f000 int main (void ) { u16 a; RCC_Configuration(); LED_Init(); KEY_Init(); BUZZER_Init(); BUZZER_BEEP1(); a = FLASH_R(FLASH_START_ADDR); GPIO_Write(LEDPORT, a | 0xfffc & GPIO_ReadOutputData(LEDPORT)); while (1 ) { if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { a++; if (a > 3 ) { a = 0 ; } GPIO_Write(LEDPORT, a | 0xfffc & GPIO_ReadOutputData(LEDPORT)); BUZZER_BEEP1(); FLASH_W(FLASH_START_ADDR, a); while (!GPIO_ReadInputDataBit(KEYPORT, KEY1)); } } } }
buzzer.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __BUZZER_H #define __BUZZER_H #include "sys.h" #define BUZZERPORT GPIOB #define BUZZER GPIO_Pin_5 void BUZZER_Init (void ) ; void BUZZER_BEEP1 (void ) ; #endif
buzzer.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "buzzer.h" #include "delay.h" void BUZZER_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = BUZZER; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(BUZZERPORT, &GPIO_InitStructure); GPIO_WriteBit(BUZZERPORT, BUZZER, (BitAction)(1 )); } void BUZZER_BEEP1 (void ) { u16 i; for (i = 0 ; i < 200 ; i++) { GPIO_WriteBit(BUZZERPORT, BUZZER, (BitAction)(0 )); delay_us(500 ); GPIO_WriteBit(BUZZERPORT, BUZZER, (BitAction)(1 )); delay_us(500 ); } }
播放 MIDI
MIDI(乐器数字接口,Musical Instrument Digital
Interface)采用音符的数字控制信号来记录音乐,即 MIDI
音乐传输的并非声音信号本身,而是 MIDI 控制指令, MIDI
信号传输时通常采用异步串行方式,
波特率为31.25 × (1 ± 0.01) KBaud
。
低音 1
262 Hz
中音 1
523 Hz
高音 1
1046 Hz
低音 1#
277 Hz
中音 1#
554 Hz
高音 1#
1109 Hz
低音 2
294 Hz
中音 2
587 Hz
高音 2
1175 Hz
低音 2#
311 Hz
中音 2#
622 Hz
高音 2#
1245 Hz
低音 3
330 Hz
中音 3
659 Hz
高音 3
1318 Hz
低音 4
349 Hz
中音 4
698 Hz
高音 4
1397 Hz
低音 4#
370 Hz
中音 4#
740 Hz
高音 4#
1480 Hz
低音 5
392 Hz
中音 5
784 Hz
高音 5
1568 Hz
低音 5#
415 Hz
中音 5#
831 Hz
高音 5#
1661 Hz
低音 6
440 Hz
中音 6
880 Hz
高音 6
1760 Hz
低音 6#
466 Hz
中音 6#
932 Hz
高音 6#
1865 Hz
低音 7
494 Hz
中音 7
988 Hz
高音 7
1976 Hz
本实验代码基于前一个蜂鸣器实验的代码,仅仅是添加了 MIDI
播放相关的头文件与函数,并在主函数中对该函数进行了调用。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include "buzzer.h" #include "delay.h" #include "flash.h" #include "key.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" #define FLASH_START_ADDR 0x0801f000 int main (void ) { u16 a; RCC_Configuration(); LED_Init(); KEY_Init(); BUZZER_Init(); MIDI_PLAY(); a = FLASH_R(FLASH_START_ADDR); GPIO_Write(LEDPORT, a | 0xfffc & GPIO_ReadOutputData(LEDPORT)); while (1 ) { if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { a++; if (a > 3 ) { a = 0 ; } GPIO_Write(LEDPORT, a | 0xfffc & GPIO_ReadOutputData(LEDPORT)); BUZZER_BEEP1(); FLASH_W(FLASH_START_ADDR, a); while (!GPIO_ReadInputDataBit(KEYPORT, KEY1)); } } } }
buzzer.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef __BUZZER_H #define __BUZZER_H #include "sys.h" #define BUZZERPORT GPIOB #define BUZZER GPIO_Pin_5 void BUZZER_Init (void ) ; void BUZZER_BEEP1 (void ) ; void MIDI_PLAY (void ) ; #endif
buzzer.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include "buzzer.h" #include "delay.h" void BUZZER_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = BUZZER; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(BUZZERPORT, &GPIO_InitStructure); GPIO_WriteBit(BUZZERPORT, BUZZER, (BitAction)(1 )); } void BUZZER_BEEP1 (void ) { u16 i; for (i = 0 ; i < 200 ; i++) { GPIO_WriteBit(BUZZERPORT, BUZZER, (BitAction)(0 )); delay_us(500 ); GPIO_WriteBit(BUZZERPORT, BUZZER, (BitAction)(1 )); delay_us(500 ); } } uc16 music1[78 ] = { 330 , 750 , 440 , 375 , 494 , 375 , 523 , 750 , 587 , 375 , 659 , 375 , 587 , 750 , 494 , 375 , 392 , 375 , 440 , 1500 , 330 , 750 , 440 , 375 , 494 , 375 , 523 , 750 , 587 , 375 , 659 , 375 , 587 , 750 , 494 , 375 , 392 , 375 , 784 , 1500 , 659 , 750 , 698 , 375 , 784 , 375 , 880 , 750 , 784 , 375 , 698 , 375 , 659 , 750 , 587 , 750 , 659 , 750 , 523 , 375 , 494 , 375 , 440 , 750 , 440 , 375 , 494 , 375 , 523 , 750 , 523 , 750 , 494 , 750 , 392 , 750 , 440 , 3000 }; void MIDI_PLAY (void ) { u16 i, e; for (i = 0 ; i < 39 ; i++) { for (e = 0 ; e < music1[i * 2 ] * music1[i * 2 + 1 ] / 1000 ; e++) { GPIO_WriteBit(BUZZERPORT, BUZZER, (BitAction)(0 )); delay_us(500000 / music1[i * 2 ]); GPIO_WriteBit(BUZZERPORT, BUZZER, (BitAction)(1 )); delay_us(500000 / music1[i * 2 ]); } } }
USART 串行通信
USART 发送
本程序示例代码基于上一步播放 MIDI
音乐的项目构建,只是往Lib 文件夹内增加了stm32f10x_usart.c
库文件,以及向Basic 文件夹添加了
USART 相关的支持代码。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include "delay.h" #include "stm32f10x.h" #include "sys.h" #include "usart.h" int main (void ) { u8 a = 7 , b = 8 ; RCC_Configuration(); USART1_Init(115200 ); while (1 ) { USART_SendData(USART1, 0x55 ); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) ; printf ("STM32F103 " ); printf ("STM32 %d %d " , a, b); USART1_printf("STM32 %d %d " , a, b); delay_ms(1000 ); } }
usart.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #ifndef __USART_H #define __USART_H #include "stdio.h" #include "sys.h" #include <stdarg.h> #include <stdlib.h> #include <string.h> #define USART_n USART1 #define USART1_REC_LEN 200 #define USART2_REC_LEN 200 #define USART3_REC_LEN 200 #define EN_USART1 1 #define EN_USART2 0 #define EN_USART3 0 extern u8 USART1_RX_BUF[USART1_REC_LEN]; extern u8 USART2_RX_BUF[USART2_REC_LEN]; extern u8 USART3_RX_BUF[USART3_REC_LEN]; extern u16 USART1_RX_STA; extern u16 USART2_RX_STA; extern u16 USART3_RX_STA; void USART1_Init (u32 bound) ; void USART2_Init (u32 bound) ; void USART3_Init (u32 bound) ; void USART1_printf (char *fmt, ...) ; void USART2_printf (char *fmt, ...) ; void USART3_printf (char *fmt, ...) ; #endif
usart.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 #include "sys.h" #include "usart.h" #if 1 #pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; _sys_exit(int x) { x = x; } int fputc (int ch, FILE *f) { while ((USART_n->SR & 0X40 ) == 0 ); USART_n->DR = (u8)ch; return ch; } #endif #if EN_USART1 u8 USART1_RX_BUF[USART1_REC_LEN]; u16 USART1_RX_STA = 0 ; void USART1_printf (char *fmt, ...) { char buffer[USART1_REC_LEN + 1 ]; u8 i = 0 ; va_list arg_ptr; va_start(arg_ptr, fmt); vsnprintf(buffer, USART1_REC_LEN + 1 , fmt, arg_ptr); while ((i < USART1_REC_LEN) && (i < strlen (buffer))) { USART_SendData(USART1, (u8)buffer[i++]); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) ; } va_end(arg_ptr); } void USART1_Init (u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE); } void USART1_IRQHandler (void ) { u8 Res; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { Res = USART_ReceiveData(USART1); printf ("%c" , Res); if ((USART1_RX_STA & 0x8000 ) == 0 ) { if (USART1_RX_STA & 0x4000 ) { if (Res != 0x0a ) USART1_RX_STA = 0 ; else USART1_RX_STA |= 0x8000 ; } else { if (Res == 0x0d ) USART1_RX_STA |= 0x4000 ; else { USART1_RX_BUF[USART1_RX_STA & 0X3FFF ] = Res; USART1_RX_STA++; if (USART1_RX_STA > (USART1_REC_LEN - 1 )) USART1_RX_STA = 0 ; } } } } } #endif #if EN_USART2 u8 USART2_RX_BUF[USART2_REC_LEN]; u16 USART2_RX_STA = 0 ; void USART2_printf (char *fmt, ...) { char buffer[USART2_REC_LEN + 1 ]; u8 i = 0 ; va_list arg_ptr; va_start(arg_ptr, fmt); vsnprintf(buffer, USART2_REC_LEN + 1 , fmt, arg_ptr); while ((i < USART2_REC_LEN) && (i < strlen (buffer))) { USART_SendData(USART2, (u8)buffer[i++]); while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET) ; } va_end(arg_ptr); } void USART2_Init (u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_Cmd(USART2, ENABLE); } void USART2_IRQHandler (void ) { u8 Res; if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { Res = USART_ReceiveData(USART2); printf ("%c" , Res); if ((USART2_RX_STA & 0x8000 ) == 0 ) { if (USART2_RX_STA & 0x4000 ) { if (Res != 0x0a ) USART2_RX_STA = 0 ; else USART2_RX_STA |= 0x8000 ; } else { if (Res == 0x0d ) USART2_RX_STA |= 0x4000 ; else { USART2_RX_BUF[USART2_RX_STA & 0X3FFF ] = Res; USART2_RX_STA++; if (USART2_RX_STA > (USART2_REC_LEN - 1 )) USART2_RX_STA = 0 ; } } } } } #endif #if EN_USART3 u8 USART3_RX_BUF[USART3_REC_LEN]; u16 USART3_RX_STA = 0 ; void USART3_printf (char *fmt, ...) { char buffer[USART3_REC_LEN + 1 ]; u8 i = 0 ; va_list arg_ptr; va_start(arg_ptr, fmt); vsnprintf(buffer, USART3_REC_LEN + 1 , fmt, arg_ptr); while ((i < USART3_REC_LEN) && (i < strlen (buffer))) { USART_SendData(USART3, (u8)buffer[i++]); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); } va_end(arg_ptr); } void USART3_Init (u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART3, &USART_InitStructure); USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); USART_Cmd(USART3, ENABLE); } void USART3_IRQHandler (void ) { u8 Res; if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) { Res = USART_ReceiveData(USART3); if (Res == 'S' ) { USART3_RX_STA = 1 ; } else if (Res == 'K' ) { USART3_RX_STA = 2 ; } } } #endif
USART 接收
单片机接收数据主要有中断 和查询 两种方式,本实验工程基于上一步
USART 数据发送的代码。
查询方式
采用查询方式实现 USART
串口数据的接收,这里的查询是指在主循环中不断的检测数据接收标志位,如果标志位为1
就表示接收到了串口数据,然后再来对接收到的数据进行处理。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include "delay.h" #include "stm32f10x.h" #include "sys.h" #include "usart.h" int main (void ) { u8 a; RCC_Configuration(); USART1_Init(115200 ); while (1 ) { if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET) { a = USART_ReceiveData(USART1); printf ("%c" , a); } } }
usart.c
1 2 3 4 5 6 7 8 9 10 #if EN_USART1 void USART1_Init (u32 bound) { USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); } #endif
中断方式
查询方式实现比较简单,但是多任务处理时需要等待主函数查询标志位,实时性较差。为了解决这个问题,需要使用中断方式进行串口数据的接收。
usart.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #if EN_USART1 void USART1_Init (u32 bound) { USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } void USART1_IRQHandler (void ) { u8 a; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){ a =USART_ReceiveData(USART1); printf ("%c" ,a); } } #endif
USART 控制 LED
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include "buzzer.h" #include "delay.h" #include "key.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" #include "usart.h" int main (void ) { u8 a; RCC_Configuration(); LED_Init(); KEY_Init(); BUZZER_Init(); USART1_Init(115200 ); while (1 ) { if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET) { a = USART_ReceiveData(USART1); switch (a) { case '0' : GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0 )); printf ("%c:LED1 OFF " , a); break ; case '1' : GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); printf ("%c:LED1 ON " , a); break ; case '2' : BUZZER_BEEP1(); printf ("%c:BUZZER " , a); break ; default : break ; } } if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { while (!GPIO_ReadInputDataBit(KEYPORT, KEY1)); printf ("KEY1 " ); } } if (!GPIO_ReadInputDataBit(KEYPORT, KEY2)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(KEYPORT, KEY2)) { while (!GPIO_ReadInputDataBit(KEYPORT, KEY2)); printf ("KEY2 " ); } } } }
usart.c
1 2 3 4 5 6 7 8 9 10 #if EN_USART1 void USART1_Init (u32 bound) { USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); } #endif
终端 USART 控制 LED
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include "buzzer.h" #include "delay.h" #include "key.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" #include "usart.h" int main (void ) { RCC_Configuration(); LED_Init(); KEY_Init(); BUZZER_Init(); USART1_Init(115200 ); USART1_RX_STA = 0xC000 ; while (1 ) { if (USART1_RX_STA & 0xC000 ) { if ((USART1_RX_STA & 0x3FFF ) == 0 ) { printf ("\033[1;47;33m\r\n" ); printf (" 1y--开LED1灯 1n--关LED1灯 \r\n" ); printf (" 2y--开LED2灯 2n--关LED2灯 \r\n" ); printf (" 请输入控制指令,按回车键执行! \033[0m\r\n" ); } else if ((USART1_RX_STA & 0x3FFF ) == 2 && USART1_RX_BUF[0 ] == '1' && USART1_RX_BUF[1 ] == 'y' ) { GPIO_SetBits(LEDPORT, LED1); printf ("1y -- LED1灯已经点亮!\r\n" ); } else if ((USART1_RX_STA & 0x3FFF ) == 2 && USART1_RX_BUF[0 ] == '1' && USART1_RX_BUF[1 ] == 'n' ) { GPIO_ResetBits(LEDPORT, LED1); printf ("1n -- LED1灯已经熄灭!\r\n" ); } else if ((USART1_RX_STA & 0x3FFF ) == 2 && USART1_RX_BUF[0 ] == '2' && USART1_RX_BUF[1 ] == 'y' ) { GPIO_SetBits(LEDPORT, LED2); printf ("2y -- LED2灯已经点亮!\r\n" ); } else if ((USART1_RX_STA & 0x3FFF ) == 2 && USART1_RX_BUF[0 ] == '2' && USART1_RX_BUF[1 ] == 'n' ) { GPIO_ResetBits(LEDPORT, LED2); printf ("2n -- LED2灯已经熄灭!\r\n" ); } else { printf ("指令错误!\r\n" ); } USART1_RX_STA = 0 ; } } }
usart.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #if EN_USART1 void USART1_Init (u32 bound) { USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } void USART1_IRQHandler (void ) { u8 Res; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { Res = USART_ReceiveData(USART1); printf ("%c" , Res); if ((USART1_RX_STA & 0x8000 ) == 0 ) { if (USART1_RX_STA & 0x4000 ) { if (Res != 0x0a ) USART1_RX_STA = 0 ; else USART1_RX_STA |= 0x8000 ; } else { if (Res == 0x0d ) USART1_RX_STA |= 0x4000 ; else { USART1_RX_BUF[USART1_RX_STA & 0X3FFF ] = Res; USART1_RX_STA++; if (USART1_RX_STA > (USART1_REC_LEN - 1 )) USART1_RX_STA = 0 ; } } } } } #endif
RTC 实时时钟
STM32F103 的备份寄存器可以保存20 Byte
的用户数据,而且与
RTC 实时时钟一样独立工作,并不会被系统、电源等复位方式复位。RTC
拥有多个时钟源输入,可以使用外部的32.768KHz
晶振配合 20
位预分频器产生一个 1 秒的时间基准信号。
STM32 的 RTC 实时时钟使用一个 32 位计数器(可计时 136
年)进行计时,计时的起始基准时间为 Unix
的1970年1月1日 0时0分0秒
。如果要读取当前的时间值,可以先读取
32 位的 RTC 计数值,然后以前面的 Unix
时间作为起点,加上计数器中的秒数,再换算为年月日时分秒格式,即可得到当前的实时时间。
本实验继续延用前面的工程项目,但是向Basic 文件夹添加了rtc.c
和rtc.h
两个文件,并引入了stm32f10x_rtc.c
库文件。实验电路中的LED1 以秒为单位,当秒数值为奇数时
LED 点亮,为偶数时 LED
熄灭,LED2 以分钟为单位,当分钟值为奇数时 LED
点亮,为偶数时 LED 熄灭。
LED 间隔 1 秒/分 闪烁
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include "buzzer.h" #include "delay.h" #include "key.h" #include "led.h" #include "rtc.h" #include "stm32f10x.h" #include "sys.h" #include "usart.h" int main (void ) { RCC_Configuration(); RTC_Config(); LED_Init(); KEY_Init(); BUZZER_Init(); USART1_Init(115200 ); USART1_RX_STA = 0xC000 ; while (1 ) { if (RTC_Get() == 0 ) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(rsec % 2 )); GPIO_WriteBit(LEDPORT, LED2, (BitAction)(rmin % 2 )); } } }
rtc.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef __RTC_H #define __RTC_H #include "sys.h" extern u16 ryear;extern u8 rmon, rday, rhour, rmin, rsec, rweek;void RTC_First_Config (void ) ; void RTC_Config (void ) ; u8 RTC_Get (void ) ; u8 RTC_Set (u16 syear, u8 smon, u8 sday, u8 hour, u8 min, u8 sec) ; u8 Is_Leap_Year (u16 year) ; u8 RTC_Get_Week (u16 year, u8 month, u8 day) ; #endif
rtc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 #include "rtc.h" #include "sys.h" u16 ryear; u8 rmon, rday, rhour, rmin, rsec, rweek; void RTC_First_Config (void ) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); BKP_DeInit(); RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); RTC_WaitForLastTask(); RTC_SetPrescaler(32767 ); RTC_WaitForLastTask(); } void RTC_Config (void ) { if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5 ) { RTC_First_Config(); BKP_WriteBackupRegister(BKP_DR1, 0xA5A5 ); } else { if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET) { } else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET) { } RCC_ClearFlag(); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); } #ifdef RTCClockOutput_Enable RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); BKP_TamperPinCmd(DISABLE); BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock); #endif } void RTC_IRQHandler (void ) { if (RTC_GetITStatus(RTC_IT_SEC) != RESET) { } RTC_ClearITPendingBit(RTC_IT_SEC); RTC_WaitForLastTask(); } void RTCAlarm_IRQHandler (void ) { if (RTC_GetITStatus(RTC_IT_ALR) != RESET) { } RTC_ClearITPendingBit(RTC_IT_ALR); RTC_WaitForLastTask(); } u8 Is_Leap_Year (u16 year) { if (year % 4 == 0 ) { if (year % 100 == 0 ) { if (year % 400 == 0 ) return 1 ; else return 0 ; } else return 1 ; } else return 0 ; } u8 const table_week[12 ] = {0 , 3 , 3 , 6 , 1 , 4 , 6 , 2 , 5 , 0 , 3 , 5 }; const u8 mon_table[12 ] = {31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 }; u8 RTC_Set (u16 syear, u8 smon, u8 sday, u8 hour, u8 min, u8 sec) { u16 t; u32 seccount = 0 ; if (syear < 2000 || syear > 2099 ) return 1 ; for (t = 1970 ; t < syear; t++) { if (Is_Leap_Year(t)) seccount += 31622400 ; else seccount += 31536000 ; } smon -= 1 ; for (t = 0 ; t < smon; t++) { seccount += (u32)mon_table[t] * 86400 ; if (Is_Leap_Year(syear) && t == 1 ) seccount += 86400 ; } seccount += (u32)(sday - 1 ) * 86400 ; seccount += (u32)hour * 3600 ; seccount += (u32)min * 60 ; seccount += sec; RTC_First_Config(); BKP_WriteBackupRegister(BKP_DR1, 0xA5A5 ); RTC_SetCounter(seccount); RTC_WaitForLastTask(); return 0 ; } u8 RTC_Get (void ) { static u16 daycnt = 0 ; u32 timecount = 0 ; u32 temp = 0 ; u16 temp1 = 0 ; timecount = RTC_GetCounter(); temp = timecount / 86400 ; if (daycnt != temp) { daycnt = temp; temp1 = 1970 ; while (temp >= 365 ) { if (Is_Leap_Year(temp1)) { if (temp >= 366 ) temp -= 366 ; else { temp1++; break ; } } else temp -= 365 ; temp1++; } ryear = temp1; temp1 = 0 ; while (temp >= 28 ) { if (Is_Leap_Year(ryear) && temp1 == 1 ) { if (temp >= 29 ) temp -= 29 ; else break ; } else { if (temp >= mon_table[temp1]) temp -= mon_table[temp1]; else break ; } temp1++; } rmon = temp1 + 1 ; rday = temp + 1 ; } temp = timecount % 86400 ; rhour = temp / 3600 ; rmin = (temp % 3600 ) / 60 ; rsec = (temp % 3600 ) % 60 ; rweek = RTC_Get_Week(ryear, rmon, rday); return 0 ; } u8 RTC_Get_Week (u16 year, u8 month, u8 day) { u16 temp2; u8 yearH, yearL; yearH = year / 100 ; yearL = year % 100 ; if (yearH > 19 ) yearL += 100 ; temp2 = yearL + yearL / 4 ; temp2 = temp2 % 7 ; temp2 = temp2 + day + table_week[month - 1 ]; if (yearL % 4 == 0 && month < 3 ) temp2--; return (temp2 % 7 ); }
终端日历程序
本示例基于上一步实验的工程文件,仅在main()
函数开始位置定义了一个
8
位变量bya
,然后在主函数的while()
循环当中,加入了串口识别以及
RTC
操作相关的代码,最后在usart.c
初始化代码的尾部使能了串口中断。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include "buzzer.h" #include "delay.h" #include "key.h" #include "led.h" #include "rtc.h" #include "stm32f10x.h" #include "sys.h" #include "usart.h" int main (void ) { u8 bya; RCC_Configuration(); RTC_Config(); LED_Init(); KEY_Init(); BUZZER_Init(); USART1_Init(115200 ); USART1_RX_STA = 0xC000 ; while (1 ) { if (USART1_RX_STA & 0xC000 ) { if ((USART1_RX_STA & 0x3FFF ) == 0 ) { if (RTC_Get() == 0 ) { printf (" RTC 实时时钟测试程序 \r\n" ); printf (" 现在实时时间:%d-%d-%d %d:%d:%d " , ryear, rmon, rday, rhour, rmin, rsec); if (rweek == 0 ) printf ("星期日 \r\n" ); if (rweek == 1 ) printf ("星期一 \r\n" ); if (rweek == 2 ) printf ("星期二 \r\n" ); if (rweek == 3 ) printf ("星期三 \r\n" ); if (rweek == 4 ) printf ("星期四 \r\n" ); if (rweek == 5 ) printf ("星期五 \r\n" ); if (rweek == 6 ) printf ("星期六 \r\n" ); printf (" 按下回车键更新时间,输入字母 C 初始化时钟 \r\n" ); printf (" 设置时间,格式为 20170806120000,并按回车键确定 \r\n" ); } else { printf ("读取失败\r\n" ); } } else if ((USART1_RX_STA & 0x3FFF ) == 1 ) { if (USART1_RX_BUF[0 ] == 'c' || USART1_RX_BUF[0 ] == 'C' ) { RTC_First_Config(); BKP_WriteBackupRegister(BKP_DR1, 0xA5A5 ); printf ("初始化成功! \r\n" ); } else { printf ("指令错误! \r\n" ); } } else if ((USART1_RX_STA & 0x3FFF ) == 14 ) { ryear = (USART1_RX_BUF[0 ] - 0x30 ) * 1000 + (USART1_RX_BUF[1 ] - 0x30 ) * 100 + (USART1_RX_BUF[2 ] - 0x30 ) * 10 + USART1_RX_BUF[3 ] - 0x30 ; rmon = (USART1_RX_BUF[4 ] - 0x30 ) * 10 + USART1_RX_BUF[5 ] - 0x30 ; rday = (USART1_RX_BUF[6 ] - 0x30 ) * 10 + USART1_RX_BUF[7 ] - 0x30 ; rhour = (USART1_RX_BUF[8 ] - 0x30 ) * 10 + USART1_RX_BUF[9 ] - 0x30 ; rmin = (USART1_RX_BUF[10 ] - 0x30 ) * 10 + USART1_RX_BUF[11 ] - 0x30 ; rsec = (USART1_RX_BUF[12 ] - 0x30 ) * 10 + USART1_RX_BUF[13 ] - 0x30 ; bya = RTC_Set(ryear, rmon, rday, rhour, rmin, rsec); if (bya == 0 ) printf ("写入成功! \r\n" ); else printf ("写入失败! \r\n" ); } else { printf ("指令错误! \r\n" ); } USART1_RX_STA = 0 ; } } }
usart.c
1 2 3 4 5 6 7 8 9 10 #if EN_USART1 void USART1_Init (u32 bound) { USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } #endif
RCC 复位与时钟控制器
RCC 是复位和时钟控制器(Reset Clock
Control)的英文缩写,用于设置单片机的复位以及系统时钟的分配(选择输入的时钟源),上面的实验程序开头部分都会调用到
RCC 设置函数。
rtc.c
1 2 3 4 5 6 7 8 9 10 11 void RTC_First_Config (void ) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); }
main.c
1 2 3 4 5 int main (void ) { RCC_Configuration(); }
sys.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void RCC_Configuration (void ) { ErrorStatus HSEStartUpStatus; RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); HSEStartUpStatus = RCC_WaitForHSEStartUp(); if (HSEStartUpStatus == SUCCESS) { RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); RCC_PCLK2Config(RCC_HCLK_Div1); FLASH_SetLatency(FLASH_Latency_2); FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() != 0x08 ) ; } }
TTP223 触摸按键
TTP223 是一款单通道电容触摸按键检测芯片,工作电压在2.0V ~ 5.5V
范围之间,灵敏度可由0 ~ 50pF
的外部电容进行调节。
上面电路图当中,触摸按键TTP223_1 、TTP223_2 、TTP223_3 、TTP223_4 经过
P10 跳线座,分别连接到 STM32 的
PA0 、PA1 、PA2 、PA3 四个引脚,并且中间并联了一枚
LED
指示灯以及限流电阻。按键电路上15pF
的C3 电容用来进行灵敏度调节,而C4 则属于滤波电容。
1
Q
O
触摸按键的输出管脚
2
VSS
P
电源负极接地端
3
I
IO
传感器输入,连接至金属触摸按键上面
4
AHLB
I-PL
选择输出电平,默认为1
表示低电平有效,如果为0
表示高电平有效
5
VDD
P
电源正极输入引脚
6
TOG
I-PL
输出类型选择,默认为高电平1
触发模式(即带有按键锁存),如果为0
表示直接模式
注意: TTP223
电容触摸芯片在上电瞬间,会读取感测电极的电容状态,并以此作为按键没有按下时的初始状态;因此,上电的一瞬间手指不能放置到按键上面。
单击控制核心板 LED
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include "delay.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" int main (void ) { RCC_Configuration(); LED_Init(); TOUCH_KEY_Init(); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { GPIO_WriteBit(LEDPORT, LED2, (BitAction)(1 )); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { GPIO_WriteBit(LEDPORT, LED1 | LED2, (BitAction)(0 )); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { GPIO_WriteBit(LEDPORT, LED1 | LED2, (BitAction)(1 )); } } }
touch_key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef __TOUCH_KEY_H #define __TOUCH_KEY_H #include "sys.h" #define TOUCH_KEYPORT GPIOA #define TOUCH_KEY_A GPIO_Pin_0 #define TOUCH_KEY_B GPIO_Pin_1 #define TOUCH_KEY_C GPIO_Pin_2 #define TOUCH_KEY_D GPIO_Pin_3 void TOUCH_KEY_Init (void ) ; #endif
touch_key.c
1 2 3 4 5 6 7 8 9 10 #include "touch_key.h" void TOUCH_KEY_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = TOUCH_KEY_A | TOUCH_KEY_B | TOUCH_KEY_C | TOUCH_KEY_D; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(TOUCH_KEYPORT, &GPIO_InitStructure); }
双击/长按控制核心板 LED
本实验项目沿用上面的源代码,未进行目录结构上的任何修改,仅在main.c
文件当中添加了#define KEYA_SPEED1 100
和#define KEYA_SPEED2 10
两条用于区分长按与双击时间的宏定义语句,下面是触摸按键单击、双击、长按时产生的信号时序示意图:
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include "delay.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" #define KEYA_SPEED1 100 #define KEYA_SPEED2 10 int main (void ) { u8 a = 0 , b, c = 0 ; RCC_Configuration(); LED_Init(); TOUCH_KEY_Init(); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { while ((!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) && c < KEYA_SPEED1) { c++; delay_ms(10 ); } if (c >= KEYA_SPEED1) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)); } else { for (b = 0 ; b < KEYA_SPEED2; b++) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { a = 1 ; GPIO_WriteBit(LEDPORT, LED2, (BitAction)(1 )); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)); } } if (a == 0 ) { GPIO_WriteBit(LEDPORT, LED1 | LED2, (BitAction)(0 )); } } a = 0 ; c = 0 ; } } } }
滑动控制 LED
需要进行滑动操作的 A、B、C、D
四个触摸按键间距必须足够的小,才会呈现出A - AB - B - BC - C - CD - D
的滑动轨迹,而非仅仅被识别为
4 次点击操作。
本实验项目同样基于上一步代码进行修改,引入了#include "usart.h"
串口操作相关的头文件,便于在main.c
当中将滑动操作的轨迹打印到
USART 串口上面。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 #include "delay.h" #include "led.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" #include "usart.h" #define KEYA_SPEED1 100 #define KEYA_SPEED2 10 int main (void ) { u16 k = 1000 ; u8 a = 0 , b, c = 0 ; u8 s = 0 ; RCC_Configuration(); USART1_Init(115200 ); LED_Init(); TOUCH_KEY_Init(); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { while ((!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) && c < KEYA_SPEED1) { c++; delay_ms(10 ); } if (c >= KEYA_SPEED1) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); printf ("A键长按 \r\n" ); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)); } else { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { k++; printf ("A键右滑 %d \r\n" , k); a = 1 ; s = 1 ; } if (a == 0 ) { for (b = 0 ; b < KEYA_SPEED2; b++) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { a = 1 ; GPIO_WriteBit(LEDPORT, LED2, (BitAction)(1 )); printf ("A键双击 \r\n" ); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)); } } if (a == 0 ) { if (s == 1 ) { s = 0 ; } else { GPIO_WriteBit(LEDPORT, LED1 | LED2, (BitAction)(0 )); printf ("A键单击 \r\n" ); } } } } a = 0 ; c = 0 ; } } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { while ((!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) && c < KEYA_SPEED1) { c++; delay_ms(10 ); } if (c >= KEYA_SPEED1) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); printf ("B键长按 \r\n" ); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)); } else { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { k++; printf ("B键右滑 %d \r\n" , k); a = 1 ; s = 1 ; } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { k++; printf ("B键左滑 %d \r\n" , k); a = 1 ; s = 1 ; } if (a == 0 ) { for (b = 0 ; b < KEYA_SPEED2; b++) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { a = 1 ; GPIO_WriteBit(LEDPORT, LED2, (BitAction)(1 )); printf ("B键双击 \r\n" ); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)); } } if (a == 0 ) { if (s == 1 ) { s = 0 ; } else { GPIO_WriteBit(LEDPORT, LED1 | LED2, (BitAction)(0 )); printf ("B键单击 \r\n" ); } } } } a = 0 ; c = 0 ; } } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { while ((!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) && c < KEYA_SPEED1) { c++; delay_ms(10 ); } if (c >= KEYA_SPEED1) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); printf ("C键长按 \r\n" ); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)); } else { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { k++; printf ("C键右滑 %d \r\n" , k); a = 1 ; s = 1 ; } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { k++; printf ("C键左滑 %d \r\n" , k); a = 1 ; s = 1 ; } if (a == 0 ) { for (b = 0 ; b < KEYA_SPEED2; b++) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { a = 1 ; GPIO_WriteBit(LEDPORT, LED2, (BitAction)(1 )); printf ("C键双击 \r\n" ); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)); } } if (a == 0 ) { if (s == 1 ) { s = 0 ; } else { GPIO_WriteBit(LEDPORT, LED1 | LED2, (BitAction)(0 )); printf ("C键单击 \r\n" ); } } } } a = 0 ; c = 0 ; } } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { while ((!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) && c < KEYA_SPEED1) { c++; delay_ms(10 ); } if (c >= KEYA_SPEED1) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); printf ("D键长按 \r\n" ); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)); } else { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { k++; printf ("D键左滑 %d \r\n" , k); a = 1 ; s = 1 ; } if (a == 0 ) { for (b = 0 ; b < KEYA_SPEED2; b++) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { a = 1 ; GPIO_WriteBit(LEDPORT, LED2, (BitAction)(1 )); printf ("D键双击 \r\n" ); while (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)); } } if (a == 0 ) { if (s == 1 ) { s = 0 ; } else { GPIO_WriteBit(LEDPORT, LED1 | LED2, (BitAction)(0 )); printf ("D键单击 \r\n" ); } } } } a = 0 ; c = 0 ; } } } }
TM1640 驱动共阴极数码管
TM1640 由深圳天威电子推出的一款数码管专用驱动控制芯片,采用 SOP28
形式封装,并使用 5V 工作电压。
DIN
数据输入
7
串行数据输入,输入数据在 SCLK 的低电平变化,在 SCLK
的高电平被传输
SCLK
时钟输入
8
在上升沿输入数据
SEG1~SEG8
输出【段】
9~16
段输出,P 管开漏输出
GRID1~GRID11 和 GRID12~GRID16
输出【位】
1~5 和 18~28
位输出,N 管开漏输出
VDD
逻辑电源
17
接电源正
VSS
逻辑地
6
接系统地
STM32F103C8T6 的通过两线式通信接口与TM1640 进行连接,当时钟信号CLK 为高电平时,数据信号DIN 保持不变;仅当CLK 为低电平时DIN 的信号才会发生改变,数据传输时总是低位在前,高位在后 。数据输入的开始条件为CLK 为高时
DIN 由高变低负跳变;结束条件是CLK 为高时DIN 由低向高正跳变,指令传输的时序图如下:
TM1640
的数据线DIN 和时钟同步线SCLK 引脚通过跳线帽P9 连接至
STM32 的PA11 和PA12 引脚,其第 17 脚和 6
脚分别连接至 5V
电源与GND ,并在回路上串接了C1 和C2 两枚滤波电容;GR1 至GR8 引脚则各自连接到共阴极数码管的位选端上,SEG1 至SEG8 引脚则连接至段选端。另外,电路中
8 枚独立 LED 的负极分别连接至 TM1640 的第 26
脚,正极则分别连接至SEG1 至SEG8 引脚。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include "TM1640.h" #include "delay.h" #include "rtc.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 c = 0x01 ; RCC_Configuration(); RTC_Config(); TM1640_Init(); while (1 ) { if (RTC_Get() == 0 ) { TM1640_display(0 , rday / 10 ); TM1640_display(1 , rday % 10 + 10 ); TM1640_display(2 , rhour / 10 ); TM1640_display(3 , rhour % 10 + 10 ); TM1640_display(4 , rmin / 10 ); TM1640_display(5 , rmin % 10 + 10 ); TM1640_display(6 , rsec / 10 ); TM1640_display(7 , rsec % 10 ); TM1640_led(c); c <<= 1 ; if (c == 0x00 ) c = 0x01 ; delay_ms(125 ); } } }
TM1640.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #ifndef __TM1640_H #define __TM1640_H #include "sys.h" #define TM1640_GPIOPORT GPIOA #define TM1640_SCLK GPIO_Pin_11 #define TM1640_DIN GPIO_Pin_12 #define TM1640_LEDPORT 0xC8 void TM1640_Init (void ) ; void TM1640_led (u8 date) ; void TM1640_display (u8 address, u8 date) ; void TM1640_display_add (u8 address, u8 date) ; #endif
TM1640.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 #include "TM1640.h" #include "delay.h" #define DEL 1 #define TM1640MEDO_ADD 0x44 #define TM1640MEDO_DISPLAY 0x8c #define TM1640MEDO_DISPLAY_OFF 0x80 void TM1640_start () { GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1 )); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1 )); delay_us(DEL); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0 )); delay_us(DEL); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0 )); delay_us(DEL); } void TM1640_stop () { GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0 )); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1 )); delay_us(DEL); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1 )); delay_us(DEL); } void TM1640_write (u8 date) { u8 i; u8 aa; aa = date; GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0 )); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0 )); for (i = 0 ; i < 8 ; i++) { GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0 )); delay_us(DEL); if (aa & 0x01 ) { GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1 )); delay_us(DEL); } else { GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0 )); delay_us(DEL); } GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1 )); delay_us(DEL); aa = aa >> 1 ; } GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0 )); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0 )); } void TM1640_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = TM1640_DIN | TM1640_SCLK; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(TM1640_GPIOPORT, &GPIO_InitStructure); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1 )); GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1 )); TM1640_start(); TM1640_write(TM1640MEDO_ADD); TM1640_stop(); TM1640_start(); TM1640_write(TM1640MEDO_DISPLAY); TM1640_stop(); } void TM1640_led (u8 date) { TM1640_start(); TM1640_write(TM1640_LEDPORT); TM1640_write(date); TM1640_stop(); } void TM1640_display (u8 address, u8 date) { const u8 buff[21 ] = {0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d , 0x7d , 0x07 , 0x7f , 0x6f , 0xbf , 0x86 , 0xdb , 0xcf , 0xe6 , 0xed , 0xfd , 0x87 , 0xff , 0xef , 0x00 }; TM1640_start(); TM1640_write(0xC0 + address); TM1640_write(buff[date]); TM1640_stop(); } void TM1640_display_add (u8 address, u8 date) { u8 i; const u8 buff[21 ] = {0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d , 0x7d , 0x07 , 0x7f , 0x6f , 0xbf , 0x86 , 0xdb , 0xcf , 0xe6 , 0xed , 0xfd , 0x87 , 0xff , 0xef , 0x00 }; TM1640_start(); TM1640_write(0xC0 + address); for (i = 0 ; i < 16 ; i++) { TM1640_write(buff[date]); } TM1640_stop(); }
EC11 旋转编码器
实验电路中所使用的 EC11
旋转编码器,旋转一圈产生的脉冲次数为20 次(即旋转
360° 需要经过 20
个位格 ),且向下按压时可作为开关使用,最大工作电压为5V
,最大工作电流为10mA
,其内部等效原理图如下所示:
当按下旋钮时,开关K1 闭合,导通第1 和2 引脚;当旋转旋钮时,开关K2 和K3 以一定的先后顺序导通,通过两者的导通顺序就可以判断旋转方向和旋转的隔断数,如下是旋钮分别向左和向右旋转时,开关K2 和K3 产生的时序差异比较图:
注意: 由于旋转编码器内部采用的是机械式微动开关,旋钮旋转过一个隔断会产生一个低电平触发,此时会在该低电平触发的下降沿和上升沿产生约2ms
左右的按键抖动。
电路图当中,将 EC11
旋转编码器的第1 和4 引脚连接至GND ,而第2 、3 、5 脚通过跳线帽P8 连接至
STM32F103
的PA6 、PA7 、PB2 引脚,此时只需要将GPIO 设置为上拉电阻输入方式,就可以在K1 、K2 、K3 任何一个开关闭合导通时,向
GPIO 端口输入低电平,从而通过这 3 个 GPIO 读取到了旋转编码器内部 3
个微动开关的状态。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include "TM1640.h" #include "delay.h" #include "encoder.h" #include "rtc.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 a = 0 , b = 0 , c = 0x01 ; RCC_Configuration(); RTC_Config(); ENCODER_Init(); TM1640_Init(); TM1640_display(0 , a / 10 ); TM1640_display(1 , a % 10 ); TM1640_display(2 , 20 ); TM1640_display(3 , 20 ); TM1640_display(4 , 20 ); TM1640_display(5 , 20 ); TM1640_display(6 , 20 ); TM1640_display(7 , 20 ); while (1 ) { b = ENCODER_READ(); if (b == 1 ) { a++; if (a > 99 ) a = 0 ; } if (b == 2 ) { if (a == 0 ) a = 100 ; a--; } if (b == 3 ) a = 0 ; if (b != 0 ) { TM1640_display(0 , a / 10 ); TM1640_display(1 , a % 10 ); } } }
encoder.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef __ENCODER_H #define __ENCODER_H #include "delay.h" #include "sys.h" #define ENCODER_PORT_A GPIOA #define ENCODER_L GPIO_Pin_6 #define ENCODER_D GPIO_Pin_7 #define ENCODER_PORT_B GPIOB #define ENCODER_R GPIO_Pin_2 void ENCODER_Init (void ) ;u8 ENCODER_READ (void ) ; #endif
encoder.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include "encoder.h" u8 KUP; u16 cou; void ENCODER_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = ENCODER_L | ENCODER_D; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(ENCODER_PORT_A, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = ENCODER_R; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(ENCODER_PORT_B, &GPIO_InitStructure); } u8 ENCODER_READ (void ) { u8 a; u8 kt; a = 0 ; if (GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_L)) KUP = 0 ; if (!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_L) && KUP == 0 ) { delay_us(100 ); kt = GPIO_ReadInputDataBit(ENCODER_PORT_B, ENCODER_R); delay_ms(3 ); if (!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_L)) { if (kt == 0 ) { a = 1 ; } else { a = 2 ; } cou = 0 ; while (!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_L) && cou < 60000 ) { cou++; KUP = 1 ; delay_us(20 ); } } } if (!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_D) && KUP == 0 ) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_D)) { a = 3 ; while (ENCODER_D == 0 ); } } return a; }
注意: 旋钮锁死是指旋钮停留在两个机械开关隔断之间的状态,卡死会造成
K2 和 K3 都处于低电平状态无法退出。
I²C 总线
LM75A 温度传感器
I²C 总线属于两线制的通信连接方式,传输线路上需要使用1kΩ ~ 10kΩ
范围阻值的上拉电阻,并且复用为
I²C 接口的 GPIO 引脚需要设置为复用开漏模式 。由于 I²C
总线上,所有设备都连接到相同的数据线
SDA 与时钟线
SCL ,因此通过每个设备的唯一地址来加以区分,该地址由 7
位十六进制数组成,一条总线上最多可挂载 127 个设备。新版 I²C 规范增加了
10 位地址模式,可以容纳的设备数量达到了 1023 个。
STM32F103C8T6 拥有 2
组I²C 总线接口,分别是由PB6 和PB7 组成的I²C1 ,由PB10 和PB11 组成的I²C2 。其中,I²C1 通过跳线帽P11 串联R18 和R19 两枚5.1kΩ
上拉电阻以后,连接至实验电路中的
OLED 液晶显示屏以及 LM75A 温度传感器。
LM75A 是由恩智浦公司生产的一款温度传感器,一共拥有 8
个引脚,其中第 1 和 2 引脚分别连接至 I²C 总线的两条通信线路,第 3
脚是中断输出,第 4 和 8
脚分别是电源的VCC 和GND ,而第 5、6、7
引脚可以用来定义设备地址。
1
SDA
串行数据输入输出。
2
SCL
串行时钟输入。
3
OS
过热时中断输出。
4
GND
电源负极接地。
5
A2
设备地址设置 2。
6
A1
设备地址设置 1。
7
A0
设备地址设置 0。
8
+Vs
电源正极供电。
注意 :实验程序中,会将 A0、A1、A2 这 3
位的值都置为1
,由于前 4
位地址固定为1001
,最低位读写位默认为 0,那么 LM75A
的设备地址应为:1001 111 0 = 0x9E
。
本实验以上一个项目作为模板修改完成,在Basic
文件夹下加入i2c.h
和i2c.c
两个程序文件,然后在Hardware
目录下添加了lm75a.h
和lm75a.c
两个源文件,最后将官方的stm32f10x_i2c.c
库文件加入到Lib
目录。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include "TM1640.h" #include "delay.h" #include "lm75a.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 buffer[3 ]; u8 c = 0x01 ; RCC_Configuration(); I2C_Configuration(); TM1640_Init(); TM1640_display(0 , 20 ); TM1640_display(1 , 20 ); TM1640_display(2 , 20 ); TM1640_display(3 , 20 ); TM1640_display(4 , 20 ); TM1640_display(5 , 20 ); TM1640_display(6 , 20 ); TM1640_display(7 , 20 ); while (1 ) { LM75A_GetTemp(buffer); TM1640_display(0 , buffer[1 ] / 10 ); TM1640_display(1 , buffer[1 ] % 10 + 10 ); TM1640_display(2 , buffer[2 ] / 10 ); TM1640_display(3 , buffer[2 ] % 10 ); TM1640_led(c); c <<= 1 ; if (c == 0x00 ) c = 0x01 ; delay_ms(150 ); } }
i2c.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef __I2C_H #define __I2C_H #include "sys.h" #define I2CPORT GPIOB #define I2C_SCL GPIO_Pin_6 #define I2C_SDA GPIO_Pin_7 #define HostAddress 0xc0 #define BusSpeed 200000 void I2C_Configuration (void ) ;void I2C_SEND_BUFFER (u8 SlaveAddr, u8 WriteAddr, u8 *pBuffer, u16 NumByteToWrite) ;void I2C_SEND_BYTE (u8 SlaveAddr, u8 writeAddr, u8 pBuffer) ;void I2C_READ_BUFFER (u8 SlaveAddr, u8 readAddr, u8 *pBuffer, u16 NumByteToRead) ;u8 I2C_READ_BYTE (u8 SlaveAddr, u8 readAddr) ; #endif
i2c.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 #include "i2c.h" void I2C_GPIO_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); GPIO_InitStructure.GPIO_Pin = I2C_SCL | I2C_SDA; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(I2CPORT, &GPIO_InitStructure); } void I2C_Configuration (void ) { I2C_InitTypeDef I2C_InitStructure; I2C_GPIO_Init(); I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 = HostAddress; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = BusSpeed; I2C_Init(I2C1, &I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); } void I2C_SEND_BUFFER (u8 SlaveAddr, u8 WriteAddr, u8 *pBuffer, u16 NumByteToWrite) { I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, SlaveAddr, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, WriteAddr); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); while (NumByteToWrite--) { I2C_SendData(I2C1, *pBuffer); pBuffer++; while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); } I2C_GenerateSTOP(I2C1, ENABLE); } void I2C_SEND_BYTE (u8 SlaveAddr, u8 writeAddr, u8 pBuffer) { I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, SlaveAddr, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, writeAddr); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, pBuffer); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); } void I2C_READ_BUFFER (u8 SlaveAddr, u8 readAddr, u8 *pBuffer, u16 NumByteToRead) { while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, SlaveAddr, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_Cmd(I2C1, ENABLE); I2C_SendData(I2C1, readAddr); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, SlaveAddr, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); while (NumByteToRead) { if (NumByteToRead == 1 ) { I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); } if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)) { *pBuffer = I2C_ReceiveData(I2C1); pBuffer++; NumByteToRead--; } } I2C_AcknowledgeConfig(I2C1, ENABLE); } u8 I2C_READ_BYTE (u8 SlaveAddr, u8 readAddr) { u8 a; while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, SlaveAddr, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_Cmd(I2C1, ENABLE); I2C_SendData(I2C1, readAddr); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, SlaveAddr, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); a = I2C_ReceiveData(I2C1); return a; }
lm75a.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __LM75A_H #define __LM75A_H #include "i2c.h" #include "sys.h" #define LM75A_ADD 0x9E void LM75A_GetTemp (u8 *Tempbuffer) ; void LM75A_POWERDOWN (void ) ; #endif
lm75a.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include "lm75a.h" void LM75A_GetTemp (u8 *Tempbuffer) { u8 buf[2 ]; u8 t = 0 , a = 0 ; I2C_READ_BUFFER(LM75A_ADD, 0x00 , buf, 2 ); t = buf[0 ]; *Tempbuffer = 0 ; if (t & 0x80 ) { *Tempbuffer = 1 ; t = ~t; t++; } if (t & 0x01 ) { a = a + 1 ; } if (t & 0x02 ) { a = a + 2 ; } if (t & 0x04 ) { a = a + 4 ; } if (t & 0x08 ) { a = a + 8 ; } if (t & 0x10 ) { a = a + 16 ; } if (t & 0x20 ) { a = a + 32 ; } if (t & 0x40 ) { a = a + 64 ; } Tempbuffer++; *Tempbuffer = a; a = 0 ; t = buf[1 ]; if (t & 0x20 ) { a = a + 12 ; } if (t & 0x40 ) { a = a + 25 ; } if (t & 0x80 ) { a = a + 50 ; } Tempbuffer++; *Tempbuffer = a; } void LM75A_POWERDOWN (void ) { I2C_SEND_BYTE(LM75A_ADD, 0x01 , 1 ); }
OLED 无字库液晶屏
12864
液晶是指128 × 64
像素分辨率的显示屏幕,当前实验电路采用的是型号为
OLED0561 的 OLED 液晶显示屏,采用
SH1106 主控芯片,可以进行单色无灰度的显示,屏幕采用 I²C
总线与STM32F103C8T6 微控制器进行通信。
在Hardware
目录下的OLED0561
文件夹内新建ASCII_8x16
(八乘十六的
ASCII
码字库)、CHS_16x16.h
(十六乘十六的汉字编码)、PIC1.h
(图片编码)以及
OLED
显示驱动程序oled0561.h
和oled0561.c
源文件。
注意: 将128 × 64
像素的屏幕划分为16 × 8
个分辨率为8 × 8
的区块,显示英文和数字时需要
2
个8 × 8
区块组成的8 × 16
分辨率区域,显示汉字则需要
4
个8 × 8
区块组成的16 × 16
分辨率区域。因此,整个屏幕每行可以显示
16 个英文和数字或者 8 个汉字,一共可以显示 4 行。
oled0561.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #ifndef __OLED_H #define __OLED_H #include "i2c.h" #include "sys.h" #define OLED0561_ADD 0x78 #define COM 0x00 #define DAT 0x40 void OLED0561_Init (void ) ; void OLED_DISPLAY_ON (void ) ; void OLED_DISPLAY_OFF (void ) ; void OLED_DISPLAY_LIT (u8 x) ; void OLED_DISPLAY_CLEAR (void ) ; void OLED_DISPLAY_8x16 (u8 x, u8 y, u16 w) ; void OLED_DISPLAY_8x16_BUFFER (u8 row, u8 *str) ; void OLED_DISPLAY_16x16 (u8 x,u8 y,u16 w) ; void OLED_DISPLAY_PIC1 (void ) ; #endif
oled0561.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include "ASCII_8x16.h" #include "CHS_16x16.h" #include "PIC1.h" #include "oled0561.h" void OLED0561_Init (void ) { OLED_DISPLAY_OFF(); OLED_DISPLAY_CLEAR(); OLED_DISPLAY_ON(); } void OLED_DISPLAY_ON (void ) { u8 buf[28 ] = { 0xae , 0x00 , 0x10 , 0xd5 , 0x80 , 0xa8 , 0x3f , 0xd3 , 0x00 , 0XB0 , 0x40 , 0x8d , 0x14 , 0xa1 , 0xc8 , 0xda , 0x12 , 0x81 , 0xff , 0xd9 , 0xf1 , 0xdb , 0x30 , 0x20 , 0x00 , 0xa4 , 0xa6 , 0xaf }; I2C_SAND_BUFFER(OLED0561_ADD, COM, buf, 28 ); } void OLED_DISPLAY_OFF (void ) { u8 buf[3 ] = { 0xae , 0x8d , 0x10 , }; I2C_SAND_BUFFER(OLED0561_ADD, COM, buf, 3 ); } void OLED_DISPLAY_LIT (u8 x) { I2C_SAND_BYTE(OLED0561_ADD, COM, 0x81 ); I2C_SAND_BYTE(OLED0561_ADD, COM, x); } void OLED_DISPLAY_CLEAR (void ) { u8 j, t; for (t = 0xB0 ; t < 0xB8 ; t++) { I2C_SAND_BYTE(OLED0561_ADD, COM, t); I2C_SAND_BYTE(OLED0561_ADD, COM, 0x10 ); I2C_SAND_BYTE(OLED0561_ADD, COM, 0x00 ); for (j = 0 ; j < 132 ; j++) { I2C_SAND_BYTE(OLED0561_ADD, DAT, 0x00 ); } } } void OLED_DISPLAY_8x16 (u8 x, u8 y, u16 w) { u8 j, t, c = 0 ; y = y + 2 ; for (t = 0 ; t < 2 ; t++) { I2C_SAND_BYTE(OLED0561_ADD, COM, 0xb0 + x); I2C_SAND_BYTE(OLED0561_ADD, COM, y / 16 + 0x10 ); I2C_SAND_BYTE(OLED0561_ADD, COM, y % 16 ); for (j = 0 ; j < 8 ; j++) { I2C_SAND_BYTE(OLED0561_ADD, DAT, ASCII_8x16[(w * 16 ) + c - 512 ]); c++; } x++; } } void OLED_DISPLAY_8x16_BUFFER (u8 row, u8 *str) { u8 r = 0 ; while (*str != '\0' ) { OLED_DISPLAY_8x16(row, r * 8 , *str++); r++; } } void OLED_DISPLAY_16x16 (u8 x, u8 y, u16 w) { u8 j, t, c = 0 ; for (t = 0 ; t < 2 ; t++) { I2C_SAND_BYTE(OLED0561_ADD, COM, 0xb0 + x); I2C_SAND_BYTE(OLED0561_ADD, COM, y / 16 + 0x10 ); I2C_SAND_BYTE(OLED0561_ADD, COM, y % 16 ); for (j = 0 ; j < 16 ; j++) { I2C_SAND_BYTE(OLED0561_ADD, DAT, GB_16[(w * 32 ) + c]); c++; } x++; } I2C_SAND_BYTE(OLED0561_ADD, COM, 0xAF ); } void OLED_DISPLAY_PIC1 (void ) { u8 m, i; for (m = 0 ; m < 8 ; m++) { I2C_SAND_BYTE(OLED0561_ADD, COM, 0xb0 + m); I2C_SAND_BYTE(OLED0561_ADD, COM, 0x10 ); I2C_SAND_BYTE(OLED0561_ADD, COM, 0x02 ); for (i = 0 ; i < 128 ; i++) { I2C_SAND_BYTE(OLED0561_ADD, DAT, PIC1[i + m * 128 ]); } } }
ASCII_8x16.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __ASCII_8x16_H #define __ASCII_8x16_H const u8 ASCII_8x16[] = { 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x80 ,0xC0 ,0x60 ,0x30 ,0x60 ,0xC0 ,0x80 ,0x00 ,0x07 ,0x07 ,0x04 ,0x04 ,0x04 ,0x07 ,0x07 ,0x00 }; #endif
CHS_16x16.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __CHS_16x16_H #define __CHS_16x16_H uc8 GB_16[] = { 0x10 ,0x22 ,0x64 ,0x0C ,0x80 ,0x08 ,0x49 ,0x4A , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 }; #endif
PIC1.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __PIC1_H #define __PIC1_H uc8 PIC1[] = { 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x7F ,0x7F ,0x77 ,0x74 ,0x67 ,0x67 ,0x63 ,0x00 }; #endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include "delay.h" #include "lm75a.h" #include "oled0561.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 buffer[3 ]; delay_ms(100 ); RCC_Configuration(); I2C_Configuration(); LM75A_GetTemp(buffer); OLED0561_Init(); OLED_DISPLAY_LIT(100 ); OLED_DISPLAY_PIC1(); delay_ms(1000 ); OLED_DISPLAY_CLEAR(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank" ); OLED_DISPLAY_8x16_BUFFER(6 , " Temp:" ); OLED_DISPLAY_16x16(2 , 2 * 16 , 0 ); OLED_DISPLAY_16x16(2 , 3 * 16 , 1 ); OLED_DISPLAY_16x16(2 , 4 * 16 , 2 ); OLED_DISPLAY_16x16(2 , 5 * 16 , 3 ); while (1 ) { LM75A_GetTemp(buffer); if (buffer[0 ]) { OLED_DISPLAY_8x16(6 , 7 * 8 , '-' ); } OLED_DISPLAY_8x16(6 , 8 * 8 , buffer[1 ] / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 9 * 8 , buffer[1 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 10 * 8 , '.' ); OLED_DISPLAY_8x16(6 , 11 * 8 , buffer[2 ] / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 12 * 8 , buffer[2 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 13 * 8 , 'C' ); delay_ms(200 ); } }
Relay 继电器
首先,将实验电路上继电器对应的P26 跳线帽短接,然后将触摸按键对应的P10 跳线帽短接,本实验使用触摸按键来进行继电器开关控制,并使用ULN2003A 达林顿芯片来驱动继电器。
上面的电路图当中,继电器 1 和 2
分别连接至STM32F103C8T6 的PA13 和PA14 引脚(上电时
JTAG 电路将这两个引脚默认为 JTAG 模式,本实验中需要将其手动设置为 GPIO
模式),然后 PA13 和 PA14
分别通过P26 跳线帽连接至ULN2003 达林顿管芯片的IN5 和IN6 引脚(由于其内部使用了非门电路,其左边输入与右边输出的状态相反),最后其OUT5/J1 和OUT6/J2 引脚分别用于控制原理图中的继电器U13 和U12 。
这样,当PA13 和PA14 输出高电平时,经过ULN2003 反向之后,对应输出低电平,由于继电器另一端连接在5V
高电平上,从而使继电器线圈吸合工作,导致OUT1A 和OUT1C 触点导通或者OUT2A 和OUT2C 触点导通;反之,如果PA13 和PA14 输出低电平,则OUT1A 和OUT1B 导通或者OUT2A 和OUT2B 导通。此外,实验电路还在继电器U13 和U12 上连接了LED9 和LED10 两枚
LED
指示灯,以及相应的R28 和R29 两只限流电阻。
realy.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef __RELAY_H #define __RELAY_H #include "sys.h" #define RELAYPORT GPIOA #define RELAY1 GPIO_Pin_14 #define RELAY2 GPIO_Pin_13 void RELAY_Init (void ) ; void RELAY_1 (u8 c) ; void RELAY_2 (u8 c) ; #endif
realy.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include "relay.h" void RELAY_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = RELAY1 | RELAY2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(RELAYPORT, &GPIO_InitStructure); GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE); GPIO_ResetBits(RELAYPORT, RELAY1 | RELAY2); } void RELAY_1 (u8 c) { GPIO_WriteBit(RELAYPORT, RELAY1, (BitAction)(c)); } void RELAY_2 (u8 c) { GPIO_WriteBit(RELAYPORT, RELAY2, (BitAction)(c)); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "delay.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" int main (void ) { RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) RELAY_1(1 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) RELAY_1(0 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) RELAY_2(1 ); if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) RELAY_2(0 ); } }
ULN2003 驱动步进电机
短接实验电路上步进电机对应的P27 跳线帽,以及触摸按键对应的P10 跳线帽,本实验将会采用触摸按键对步进电机进行控制。步进电机是一种可以通过脉冲信号数量旋转特定角度的元器件,步进电机拥有蓝A+
、粉A-
、黄B+
、橙B-
、红COM
五条连接线,向
COM
端输入高电平,其它端口输出相应的低电平,就可以让步进电机开始旋转(四拍模式下电机旋转
90 度,八拍模式下电机旋转 45
度),宣传完成之后需要及时断电,否则会造成某个线圈长时间通电烧毁。
当前实验电路采用的是五线四相步进电机(5 条接线,4
组线圈),通过STM32F103C8T6 的PB3
、PB4
、PB8
、PB9
四个引脚进行控制,它们分别连接至ULN2003 达林顿芯片输入端的MO_1/IN1
、MO_2/IN2
、MO_3/IN3
、MO_4/IN4
引脚,而输出端则连接至P20 插座上步进电机的蓝
、粉
、黄
、橙
、红
5
条导线,具体接线方式请参考前一小节继电器相关的电路图。
按键控制步进电机
step_motor.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef __STEP_MOTOR_H #define __STEP_MOTOR_H #include "delay.h" #include "sys.h" #define STEP_MOTOR_PORT GPIOB #define STEP_MOTOR_A GPIO_Pin_3 #define STEP_MOTOR_B GPIO_Pin_4 #define STEP_MOTOR_C GPIO_Pin_8 #define STEP_MOTOR_D GPIO_Pin_9 void STEP_MOTOR_Init (void ) ; void STEP_MOTOR_OFF (void ) ; void STEP_MOTOR_4S (u8 speed) ;void STEP_MOTOR_4R (u8 speed) ;void STEP_MOTOR_4L (u8 speed) ;void STEP_MOTOR_8R (u8 speed) ;void STEP_MOTOR_8L (u8 speed) ;#endif
step_motor.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 #include "step_motor.h" void STEP_MOTOR_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(STEP_MOTOR_PORT, &GPIO_InitStructure); GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE); STEP_MOTOR_OFF(); } void STEP_MOTOR_OFF (void ) { GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D); } void STEP_MOTOR_4S (u8 speed) { GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_D); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_C); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D); delay_ms(speed); STEP_MOTOR_OFF(); } void STEP_MOTOR_4R (u8 speed) { GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); delay_ms(speed); STEP_MOTOR_OFF(); } void STEP_MOTOR_4L (u8 speed) { GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); delay_ms(speed); STEP_MOTOR_OFF(); } void STEP_MOTOR_8R (u8 speed) { GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_C); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_D); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); delay_ms(speed); STEP_MOTOR_OFF(); } void STEP_MOTOR_8L (u8 speed) { GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_D); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_C); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A); delay_ms(speed); GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); delay_ms(speed); STEP_MOTOR_OFF(); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "delay.h" #include "relay.h" #include "step_motor.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" int main (void ) { RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); STEP_MOTOR_Init(); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) STEP_MOTOR_4R(3 ); else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) STEP_MOTOR_4L(3 ); else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) STEP_MOTOR_8R(3 ); else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) STEP_MOTOR_8L(3 ); else STEP_MOTOR_OFF(); } }
步进电机步数控制
step_motor.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #ifndef __STEP_MOTOR_H #define __STEP_MOTOR_H #include "delay.h" #include "sys.h" extern u8 STEP; #define STEP_MOTOR_PORT GPIOB #define STEP_MOTOR_A GPIO_Pin_3 #define STEP_MOTOR_B GPIO_Pin_4 #define STEP_MOTOR_C GPIO_Pin_8 #define STEP_MOTOR_D GPIO_Pin_9 void STEP_MOTOR_Init (void ) ; void STEP_MOTOR_OFF (void ) ; void STEP_MOTOR_8A (u8 a, u16 speed) ; void STEP_MOTOR_NUM (u8 RL, u16 num, u8 speed) ; void STEP_MOTOR_LOOP (u8 RL, u8 LOOP, u8 speed) ; #endif
step_motor.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include "step_motor.h" u8 STEP; void STEP_MOTOR_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(STEP_MOTOR_PORT, &GPIO_InitStructure); GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE); STEP_MOTOR_OFF(); } void STEP_MOTOR_OFF (void ) { GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D); } void STEP_MOTOR_8A (u8 a, u16 speed) { switch (a) { case 0 : GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A); break ; case 1 : GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); break ; case 2 : GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_C | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B); break ; case 3 : GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); break ; case 4 : GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_D); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_C); break ; case 5 : GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_C | STEP_MOTOR_D); break ; case 6 : GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_D); break ; case 7 : GPIO_ResetBits(STEP_MOTOR_PORT, STEP_MOTOR_B | STEP_MOTOR_C); GPIO_SetBits(STEP_MOTOR_PORT, STEP_MOTOR_A | STEP_MOTOR_D); break ; default : break ; } delay_ms(speed); STEP_MOTOR_OFF(); } void STEP_MOTOR_NUM (u8 RL, u16 num, u8 speed) { u16 i; for (i = 0 ; i < num; i++) { if (RL == 1 ) { STEP++; if (STEP > 7 ) STEP = 0 ; } else { if (STEP == 0 ) STEP = 8 ; STEP--; } STEP_MOTOR_8A(STEP, speed); } } void STEP_MOTOR_LOOP (u8 RL, u8 LOOP, u8 speed) { STEP_MOTOR_NUM(RL, LOOP * 4076 , speed); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "delay.h" #include "relay.h" #include "step_motor.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" int main (void ) { RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); STEP_MOTOR_Init(); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) STEP_MOTOR_LOOP(0 , 1 , 3 ); else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) STEP_MOTOR_LOOP(1 , 1 , 3 ); else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) STEP_MOTOR_NUM(0 , 100 , 3 ); else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) STEP_MOTOR_NUM(1 , 100 , 3 ); else STEP_MOTOR_OFF(); } }
RS232 串行通信
短接实验电路上RS232 对应的P13
跳线帽,并且断开RS485 对应的P22
跳线帽,以及右上角网络标号
MY1680 对应的PB10 和PB11 跳线帽。
注意: 将 RS232 的 DB9 接头最上面一排的第 2、3 针(即
RX 与 TX)用杜邦线短接在一起,可以方便的进行串口收发测试。
上面的电路图当中,STM32F103C8T6 的USART3 引脚RX/PB10
和TX/PB11
,分别通过跳线座P13 连接至SP3232 电平转换芯片的T1IN
和R1OUT
引脚,然后经过T1OUT
、R1IN
连接至DB9 插头的第
2 输入和第 3 输出针脚。此外,还将DB9 插头的第 5
脚连接至GND 。
usart.h
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" #include "usart.h" int main (void ) { u8 a; delay_ms(100 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " RS232 Test " ); OLED_DISPLAY_8x16_BUFFER(6 , "TX: RX: " ); USART3_Init(115200 ); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { USART3_printf("%c" , 'A' ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'A' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { USART3_printf("%c" , 'B' ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'B' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { USART3_printf("%c" , 'C' ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'C' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { USART3_printf("%c" , 'D' ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'D' ); } if (USART_GetFlagStatus(USART3, USART_FLAG_RXNE) != RESET) { a = USART_ReceiveData(USART3); OLED_DISPLAY_8x16(6 , 11 * 8 , a); } } }
注意: RS232 串口与之前小节介绍的 USART
串行通信没有本质区别,唯一的差别在于硬件上将 TTL 电平转换为了 RS232
电平,而这样的转换并不影响之前编写的 USART 串行驱动程序的工作。
RS485 串行通信
RS485 通信依然是基于STM32F103C8T6 的 USART
串行接口,其相比 RS232 可靠性更高传输距离更远(USART 最大传输距离 2
米,RS232 最大传输距离 20 米,RS485 最大传输距离达到 1000
米)。开始进一步学习之前,需要对实验电路当中的跳线进行设置,将实验电路上的RS232 对应的P13
跳线帽断开,然后将RS485 对应的P22
跳线帽短接,最后还需要断开右上角网络标号MY1680 对应的PB10
和PB11
跳线帽。
注意: 由于 RS485
采用差分电平(±2V ~ 6V
)通信机制,无法像 RS232
示例程序那样将 RX 和 TX 短接起来进行测试,因此必须借助于一个 USB 转
RS485 设备才能进行正确的完成通信实验。
RS485
的TX 、RX 接口分别连接至STM32F103C8T6 上面
USART3
所复用的PB11 和PB10 引脚,另外一个收发选择接口RE 则连接至一个普通的PA8 引脚(当RE 输出高电平时,后续SP3485 电平转换芯片处于发送状态,反之输出低电平就处于接收状态)。RS485
的TX 、RX 、RE 经过P22 跳线插座以后,分别连接到SP3485 电平转换芯片的RO 、DI 以及RE 和DE 引脚,然后经由A 、B 引脚输出至标号为P23 的接线端子。
注意: SP3485 电平转换芯片输出的 A 和 B
两条信号线之间连接了一枚360Ω
的电阻R30 ,然后
A 和 B
各自连接了一枚360Ω
的上拉电阻R31 和下拉电阻R32 ,这些电阻的作用是为了保证通信的稳定性。
rs485.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __RS485_H #define __RS485_H #include "sys.h" #define RS485PORT GPIOA #define RS485_RE GPIO_Pin_8 void RS485_Init (void ) ; void RS485_printf (char *fmt, ...) ; #endif
rs485.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include "rs485.h" #include "sys.h" #include "usart.h" void RS485_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = RS485_RE; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(RS485PORT, &GPIO_InitStructure); GPIO_ResetBits(RS485PORT, RS485_RE); } void RS485_printf (char *fmt, ...) { char buffer[USART3_REC_LEN + 1 ]; u8 i = 0 ; va_list arg_ptr; GPIO_SetBits(RS485PORT, RS485_RE); va_start(arg_ptr, fmt); vsnprintf(buffer, USART3_REC_LEN + 1 , fmt, arg_ptr); while ((i < USART3_REC_LEN) && (i < strlen (buffer))) { USART_SendData(USART3, (u8)buffer[i++]); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) ; } va_end(arg_ptr); GPIO_ResetBits(RS485PORT, RS485_RE); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include "delay.h" #include "oled0561.h" #include "relay.h" #include "rs485.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" #include "usart.h" int main (void ) { u8 a; delay_ms(100 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " RS485 Test " ); OLED_DISPLAY_8x16_BUFFER(6 , "TX: RX: " ); USART3_Init(115200 ); RS485_Init(); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { RS485_printf("%c" , 'A' ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'A' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { RS485_printf("%c" , 'B' ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'B' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { RS485_printf("%c" , 'C' ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'C' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { RS485_printf("%c" , 'D' ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'D' ); } if (USART_GetFlagStatus(USART3, USART_FLAG_RXNE) != RESET) { a = USART_ReceiveData(USART3); OLED_DISPLAY_8x16(6 , 11 * 8 , a); } } }
CAN 总线通信
由于实验电路当中,数码管占用了 CAN 总线实验需要的 GPIO
引脚,所以需要短接实验电路上 CAN
总线对应的P24 跳线帽,并且断开数码管对应的P9 跳线帽。
上面电路图当中的TJA1050 是 NXP 推出的 CAN
协议控制器与物理总线之间的接口转换芯片,采用了5V
电压驱动。
CAN
总线的CAN_TX
和CAN_RX
分别连接至STM32F103C8T6 的PA12
和PA11
引脚,然后经过
P24 跳线插座连接至 CAN 总线收发器 TJA1050
芯片的TXD
和RXD
引脚,然后经由CANH
和CANL
引脚输出,两条输出线之前还并联了一枚120Ω
的R33 电阻用于稳定总线工作,最后连接至P25 接线端子。
协议分析
CAN(控制器区域网络,Controller Area
Network)是一种标准化的串行通信协议,该协议经过 ISO
标准化以后分为ISO11898 高速通信标准(125Kbps ~ 1Mbps
,有效距离
40
米)和ISO11519-2 低速通信标准(10Kbps ~ 125Kbps
,有效距离
1 千米)。如果 CAN 总线上只挂载了两个设备,那么可以简单的将其视为 USART
串口来使用。而如果挂载了多个设备,则需要使用到 CAN
总线协议的邮箱、识别符、过滤器等高级特性。
CAN 的优点在于,总线空闲时所有单元都可以发送数据,当 2
个以上的总线设备同时发送数据时,根据标识符 来判断相应优先级,此时会对各条消息
ID
的每个位逐个进行仲裁,优先级最高的单元可以继续数据发送,较低的单元则立刻停止发送并进入接收工作状态。
注意: CAN
总线上的设备都必须基于相同的波特率进行通信。
CAN 总线发送部分包含有如下的 5 个概念:
报文 :CAN
设备发送出去的完整数据信息,一条数据帧或者遥控帧的报文格式如下所示:起始位 | 标识符 | 控制位 | 数据内容 | CRC校验位 | ACK位 | 结束位
;
发送邮箱 :用于报文发送的调度器,STM32F103C8T6 拥有
3
个发送邮箱,待发送数据会放入优先级最高的空邮箱,已放入数据的邮箱将会处于正在或等待发送的状态;
帧种类 :不同用途的报文种类,分为数据帧 (用于发送单元向接收单元传递数据)、遥控帧 (用于接收单元向具有相同标识的发送单元请求数据)、错误帧(用于检测到错误时向其它单元通知错误)、过载帧(用于接收单元通知其它尚未做好接收准备的单元)、帧间帧(用于分离数据/遥控帧与前面的其它帧);
标识符 :属于 CAN 报文的一部分,用于 CAN
总线设备判断数据是否发给自身,标识符不符的报文会被过滤器移除;
帧格式 :报文当中包含的内容,数据帧 和遥控帧 都具有标准 (11
个位标识符)和扩展 (29 个位标识符)两种格式;
进行数据接收时,与过滤器 (用来过滤掉与标识符不匹配的报文 )匹配的报文会被放入FIFO
邮箱 (先入先出 ),STM32F103C8T6 拥有 2
个 FIFO 接收邮箱,每个 FIFO 拥有 3
层深度。当过滤器 处于列表模式 时,必须标识符每个位都完全相同才视为匹配;处于屏蔽位模式 时,屏蔽组为1
的对应标识符位必须匹配,为0
的对应标识符位无效。过滤器的优先级基于以下规则来判定:
位宽为32 位的过滤器,优先级高于位宽为16 位的过滤器;
位宽相同的过滤器,标识符列表模式 的优先级高于屏蔽位模式 ;
位宽与模式都相同的过滤器,优先级由过滤器号 决定,号码越小优先级越高。
注意: 进行接下来的 CAN 总线收发实验之前,需要先向
Lib
文件夹添加stm32f10x_can.c
标准外设库文件。
can.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef __CAN_H #define __CAN_H #include "sys.h" #define CAN_INT_ENABLE 0 #define tsjw CAN_SJW_1tq #define tbs1 CAN_BS1_8tq #define tbs2 CAN_BS2_7tq #define brp 9 u 8 CAN1_Configuration(void ); u8 CAN_Send_Msg (u8 *msg, u8 len) ; u8 CAN_Receive_Msg (u8 *buf) ; #endif
can.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 #include "can.h" u8 CAN1_Configuration (void ) { GPIO_InitTypeDef GPIO_InitStructure; CAN_InitTypeDef CAN_InitStructure; CAN_FilterInitTypeDef CAN_FilterInitStructure; #if CAN_INT_ENABLE NVIC_InitTypeDef NVIC_InitStructure; #endif RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); CAN_InitStructure.CAN_TTCM = DISABLE; CAN_InitStructure.CAN_ABOM = DISABLE; CAN_InitStructure.CAN_AWUM = DISABLE; CAN_InitStructure.CAN_NART = ENABLE; CAN_InitStructure.CAN_RFLM = DISABLE; CAN_InitStructure.CAN_TXFP = DISABLE; CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; CAN_InitStructure.CAN_SJW = tsjw; CAN_InitStructure.CAN_BS1 = tbs1; CAN_InitStructure.CAN_BS2 = tbs2; CAN_InitStructure.CAN_Prescaler = brp; CAN_Init(CAN1, &CAN_InitStructure); CAN_FilterInitStructure.CAN_FilterNumber = 0 ; CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000 ; CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000 ; CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000 ; CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000 ; CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; CAN_FilterInit(&CAN_FilterInitStructure); #if CAN_INT_ENABLE CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); #endif return 0 ; } u8 CAN_Send_Msg (u8 *msg, u8 len) { u8 mbox; u16 i = 0 ; CanTxMsg TxMessage; TxMessage.StdId = 0x12 ; TxMessage.ExtId = 0x00 ; TxMessage.IDE = CAN_Id_Standard; TxMessage.RTR = CAN_RTR_Data; TxMessage.DLC = len; for (i = 0 ; i < len; i++) { TxMessage.Data[i] = msg[i]; } mbox = CAN_Transmit(CAN1, &TxMessage); i = 0 ; while ((CAN_TransmitStatus(CAN1, mbox) == CAN_TxStatus_Failed) && (i < 0XFFF )) { i++; if (i >= 0XFFF ) { return 1 ; } return 0 ; } } u8 CAN_Receive_Msg (u8 *buf) { u32 i; CanRxMsg RxMessage; if (CAN_MessagePending(CAN1, CAN_FIFO0) == 0 ) { return 0 ; CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); for (i = 0 ; i < 8 ; i++) { buf[i] = RxMessage.Data[i]; } return RxMessage.DLC; } } void USB_LP_CAN1_RX0_IRQHandler (void ) { CanRxMsg RxMessage; vu8 CAN_ReceiveBuff[8 ]; vu8 i = 0 ; vu8 u8_RxLen = 0 ; CAN_ReceiveBuff[0 ] = 0 ; RxMessage.StdId = 0x00 ; RxMessage.ExtId = 0x00 ; RxMessage.IDE = 0 ; RxMessage.RTR = 0 ; RxMessage.DLC = 0 ; RxMessage.FMI = 0 ; for (i = 0 ; i < 8 ; i++) { RxMessage.Data[i] = 0x00 ; } CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); u8_RxLen = RxMessage.DLC; if (RxMessage.StdId == 0x12 ) { CAN_ReceiveBuff[0 ] = RxMessage.DLC; for (i = 0 ; i < u8_RxLen; i++) { CAN_ReceiveBuff[i] = RxMessage.Data[i]; } } }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include "can.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" int main (void ) { u8 x; u8 buff[8 ]; delay_ms(100 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); CAN1_Configuration(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " CAN Test " ); OLED_DISPLAY_8x16_BUFFER(6 , "TX: RX: " ); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { buff[0 ] = 'A' ; CAN_Send_Msg(buff, 1 ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'A' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { buff[0 ] = 'B' ; CAN_Send_Msg(buff, 1 ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'B' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { buff[0 ] = 'C' ; CAN_Send_Msg(buff, 1 ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'C' ); } else if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { buff[0 ] = 'D' ; CAN_Send_Msg(buff, 1 ); OLED_DISPLAY_8x16(6 , 4 * 8 , 'D' ); } x = CAN_Receive_Msg(buff); if (x) { OLED_DISPLAY_8x16(6 , 11 * 8 , buff[0 ]); } } }
ADC 模数转换
ADC
是STM32F103C8T6 用于采集模拟值的片上外设,STM32F103C8T6 拥有
2 个 12 位分辨率(读出数据的位长度)的 ADC 模数转换器,它们共用 16
个外部通道(即 ADC
输入引脚ADC12_IN0 ~ ADC12_IN9
),并且都可使用 DMA
进行操作。此外,还有VDDA
和VSSA
引脚用于提供 ADC
功能的电源输入,为它们提供稳定的电源输入可以确保采集的精度与稳定性。
光敏电阻
首先,短接 ADC
对应的P8 跳线座,让光敏电阻与STM32F103C8T6 正常连接,然后将stm32f10x_dma
和stm32f10x_adc
两个标准外设库文件添加到项目当中。
电路图最左侧的RG1 就是光敏电阻(光线越强阻值越小,光线越弱阻值越大),将它的一个引脚接地,另一个引脚通过10K
上拉电阻连接至3.3V
电源上,然后通过1K
限流电阻经由P8 跳线插座连接至STM32F103C8T6 的PA4
和PA5
引脚。这样,光敏电阻阻值的变化,就会使得
ADC
采集对应的PA4
和PA5
引脚的输入电压发生变化。
adc.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #ifndef __ADC_H #define __ADC_H #include "sys.h" #define ADC1_DR_Address ((uint32_t)0x4001244C) #define ADCPORT GPIOA #define ADC_CH4 GPIO_Pin_4 #define ADC_CH5 GPIO_Pin_5 #define ADC_CH6 GPIO_Pin_6 #define ADC_CH7 GPIO_Pin_7 void ADC_Configuration (void ) ;void ADC_GPIO_Init (void ) ;void ADC_DMA_Init (void ) ;#endif
adc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include "adc.h" vu16 ADC_DMA_IN5; void ADC_DMA_Init (void ) { DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_DMA_IN5; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1 ; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); } void ADC_GPIO_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin = ADC_CH5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(ADCPORT, &GPIO_InitStructure); } void ADC_Configuration (void ) { ADC_InitTypeDef ADC_InitStructure; ADC_GPIO_Init(); ADC_DMA_Init(); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1 ; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1 , ADC_SampleTime_28Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include "adc.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" extern vu16 ADC_DMA_IN5; int main (void ) { delay_ms(500 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); ADC_Configuration(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " ADC Test " ); OLED_DISPLAY_8x16_BUFFER(6 , " ADC_IN5: " ); while (1 ) { OLED_DISPLAY_8x16(6 , 10 * 8 , ADC_DMA_IN5 / 1000 + 0x30 ); OLED_DISPLAY_8x16(6 , 11 * 8 , ADC_DMA_IN5 % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(6 , 12 * 8 , ADC_DMA_IN5 % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 13 * 8 , ADC_DMA_IN5 % 10 + 0x30 ); delay_ms(500 ); } }
电位器&光敏电阻 2 通道 ADC
采集
上面的光敏电阻实验只演示了 1
个通道的示例,这里再来完成一个电位器、光敏电阻的 2 通道 ADC
采集。代码执行时,OLED
屏幕会同时显示ADC_IN4 (电位器电压)和ADCIN5 (光敏电阻电压)两组数据。
adc.h
adc.h
没有进行任何改动,继续沿用上一个实验当中的头文件代码。
adc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include "adc.h" vu16 ADC_DMA_IN[2 ]; void ADC_DMA_Init (void ) { DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_DMA_IN; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 2 ; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); } void ADC_GPIO_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin = ADC_CH4 | ADC_CH5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(ADCPORT, &GPIO_InitStructure); } void ADC_Configuration (void ) { ADC_InitTypeDef ADC_InitStructure; ADC_GPIO_Init(); ADC_DMA_Init(); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 2 ; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1 , ADC_SampleTime_28Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2 , ADC_SampleTime_28Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include "adc.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" extern vu16 ADC_DMA_IN[2 ]; int main (void ) { delay_ms(500 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); ADC_Configuration(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " ADC Test " ); OLED_DISPLAY_8x16_BUFFER(4 , " ADC_IN4: " ); OLED_DISPLAY_8x16_BUFFER(6 , " ADC_IN5: " ); while (1 ) { OLED_DISPLAY_8x16(4 , 10 * 8 , ADC_DMA_IN[0 ] / 1000 + 0x30 ); OLED_DISPLAY_8x16(4 , 11 * 8 , ADC_DMA_IN[0 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(4 , 12 * 8 , ADC_DMA_IN[0 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 13 * 8 , ADC_DMA_IN[0 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 10 * 8 , ADC_DMA_IN[1 ] / 1000 + 0x30 ); OLED_DISPLAY_8x16(6 , 11 * 8 , ADC_DMA_IN[1 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(6 , 12 * 8 , ADC_DMA_IN[1 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 13 * 8 , ADC_DMA_IN[1 ] % 10 + 0x30 ); delay_ms(500 ); } }
模拟量摇杆
模拟量摇杆的机械结构由 2 个电位器和 1
个微动开关组成,进入本实验之前,需要将实验电路当中模拟量遥感对应的P17 跳线座短接,同时断开旋转编码器对应的P18 跳线,避免两者共用一组
GPIO 引脚。
模拟量摇杆的 X 轴电位器JS_X 、Y
轴电位器JS_Y 、微动开头JS_D 分别经由P17 跳线插座连接至STM32F103C8T6 的PA6
、PA7
、PB2
引脚。将下面程序下载到实验电路运行以后,OLED
屏幕显示的ADC_IN7 是摇杆上下调整的模拟量值,ADC_IN6 则是摇杆左右调整的模拟量值。如果分别向左上、右上、左下、右下摆动摇杆,则会使ADC_IN6 和ADC_IN7 两组值同时发生变化。如果直接按下摇杆,将会在
OLED 屏幕的左上角显示H
字母,放开则会直接消失。
JoyStick.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef __KEY_H #define __KEY_H #include "sys.h" #define JoyStickPORT GPIOB #define JoyStick_KEY GPIO_Pin_2 void JoyStick_Init (void ) ; #endif
JoyStick.c
1 2 3 4 5 6 7 8 9 10 11 #include "JoyStick.h" void JoyStick_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = JoyStick_KEY; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(JoyStickPORT, &GPIO_InitStructure); }
adc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void ADC_GPIO_Init (void ) { GPIO_InitStructure.GPIO_Pin = ADC_CH6 | ADC_CH7; } void ADC_Configuration (void ) { ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1 , ADC_SampleTime_28Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 2 , ADC_SampleTime_28Cycles5); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include "JoyStick.h" #include "adc.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" extern vu16 ADC_DMA_IN[2 ]; int main (void ) { delay_ms(500 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); ADC_Configuration(); JoyStick_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " ADC Test " ); OLED_DISPLAY_8x16_BUFFER(4 , " ADC_IN6: " ); OLED_DISPLAY_8x16_BUFFER(6 , " ADC_IN7: " ); while (1 ) { OLED_DISPLAY_8x16(4 , 10 * 8 , ADC_DMA_IN[0 ] / 1000 + 0x30 ); OLED_DISPLAY_8x16(4 , 11 * 8 , ADC_DMA_IN[0 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(4 , 12 * 8 , ADC_DMA_IN[0 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 13 * 8 , ADC_DMA_IN[0 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 10 * 8 , ADC_DMA_IN[1 ] / 1000 + 0x30 ); OLED_DISPLAY_8x16(6 , 11 * 8 , ADC_DMA_IN[1 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(6 , 12 * 8 , ADC_DMA_IN[1 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 13 * 8 , ADC_DMA_IN[1 ] % 10 + 0x30 ); if (GPIO_ReadInputDataBit(JoyStickPORT, JoyStick_KEY) == 0 ) { OLED_DISPLAY_8x16(0 , 0 , 'H' ); } else { OLED_DISPLAY_8x16(0 , 0 , ' ' ); } delay_ms(500 ); } }
MY1650 音频解码芯片
MY1690-16S 是深圳市迈优科技有限公司自主研发的一款串口控制的插卡音频播放芯片,可以进行
MP3 和 WAV 双格式解码,最大支持 32G 的 TF 存储卡,也可以外接 U 盘或者
USB 数据线连接至电脑,以更换 SD
卡音频文件。进行本实验之前,需要先短接实验电路右上角的 TF
CARD 、MY1680 、USB
相关的跳线座,然后再分别断接旋转编码器、模拟量摇杆对应的P18 和P17 跳线座。
STM32F103C8T6 的 USART3
外设对应的PB10
和PB11
经由上面原理图中的TFMUSIC_RX
和TFMUSIC_TX
分别连接至P14 跳线插座的RX
和TX
,最后连接到MY1690-16S 音频播放芯片的
RX
和 TX
引脚。此外,网络标号为J8 的 SD
卡座的DO
、CLK
、DI
引脚分别经由P16 跳线座连接至MY1690-16S 的MY_DAT
、MY_CMD
、MY_CLK
引脚。最后,MY1690 的ADKEY
引脚经过P16 跳线座以后,最终连接至LM4871 音频功率放大芯片的电源控制引脚SHUTDOWN
。
1
SD_DAT
接 SD 卡
-
2
SD_CMD
接 SD 卡
-
3
SD_CLK
接 SD 卡
-
4
ADKEY
电阻分压功能选择脚
通过不同阻值分压实现多种功能
5
DM
USB 信号线
U 盘或者 USB 连接电脑更换 TF 卡文件
6
DP
USB 信号线
U 盘或者 USB 连接电脑更换 TF 卡文件
7
VPN
内部电源偏置电压
1uF
电容接地
8
VSS
模拟信号地
模拟地,连接大功率功放时与数字地分开
9
DACL
右声道音频信号输出
连接耳机或者功放
10
DACR
左声道音频信号输出
连接耳机或者功放
11
3V3
内部 LDO 3.3V 输出
可以为 TF 卡,驱动电流100mA
以内
12
DC5V
芯片电源正极输入
电源范围3.4V ~ 5.5V
13
GND
系统接地
-
14
TX
UART 异步串口数据输出
3.3V
的 TTL 信号
15
RX
UART 异步串口数据输入
3.3V
的 TTL 信号
16
BUSY
播放时输出高电平,暂停或停止时为低电平
-
如前所述,MY1690-16S 的MY_DAT
、MY_CMD
、MY_CLK
是连接到
TF
卡上的,可连接控制按键盘的ADKEY
引脚通过22KΩ
电阻直接连接到3.3V
电源,另外DM
和DP
引脚用于连接开发板上的
Micro USB 接口(如果连接了 USB 接口但是没有插入 TF 卡,MY1690 会将 TF
卡的 U
盘功能切换为声卡功能)。MY1690-16S 的VPN
引脚则连接至1uF
电容,VSS
引脚直接接地,DACL
和DACR
是左右声道音频输出,当前实验电路只有一个单声道扬声器,所以各经过1uF
电容后将双声道合并为单声道经由功放芯片LM4871 音频放大后接入扬声器;BUSY
引脚连接到一枚外部
LED,芯片工作时自动点亮。
MY1690-16S 采用 3.3V
的 UART
异步串行接口连接,通讯数据格式为1 位起始位
、8 位数据位
、无奇偶校验位
、1 位停止位
;此外,其传输的数据必须遵循如下协议格式:
1 起始码(0x7E) | 长度(参考手册) | 操作码(参考手册) | 参数(参考手册) | 校验码(参考手册) | 结束码(0xEF)
音乐播放
usart.h
usart.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void USART3_Init (u32 BaudRate) { USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); } void USART3_IRQHandler (void ) { u8 Res; if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) { Res = USART_ReceiveData(USART3); if (Res == 'S' ) { USART3_RX_STA = 1 ; } else if (Res == 'K' ) { USART3_RX_STA = 2 ; } } }
MY1690.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #ifndef __MY1690_H #define __MY1690_H #include "sys.h" #include "usart.h" void MY1690_Init (void ) ; void MY1690_PLAY (void ) ; void MY1690_PREV (void ) ; void MY1690_NEXT (void ) ; void MY1690_PAUSE (void ) ; void MY1690_VUP (void ) ; void MY1690_VDOWN (void ) ; void MY1690_STOP (void ) ; void MY1690_CMD1 (u8 a) ;void MY1690_CMD2 (u8 a, u8 b) ;void MY1690_CMD3 (u8 a, u16 b) ;#endif
MY1690.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include "MY1690.h" void MY1690_Init (void ) { USART3_Init(9600 ); MY1690_STOP(); } void MY1690_PLAY (void ) { USART3_printf("\x7e\x03\x11\x12\xef" ); } void MY1690_PAUSE (void ) { USART3_printf("\x7e\x03\x12\x11\xef" ); } void MY1690_PREV (void ) { USART3_printf("\x7e\x03\x14\x17\xef" ); } void MY1690_NEXT (void ) { USART3_printf("\x7e\x03\x13\x10\xef" ); } void MY1690_VUP (void ) { USART3_printf("\x7e\x03\x15\x16\xef" ); } void MY1690_VDOWN (void ) { USART3_printf("\x7e\x03\x16\x15\xef" ); } void MY1690_STOP (void ) { USART3_printf("\x7e\x03\x1E\x1D\xef" ); } void MY1690_CMD1 (u8 a) { u8 i; i = 3 ^ a; USART_SendData(USART3, 0x7e ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, 0x03 ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, a); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, i); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, 0xef ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); } void MY1690_CMD2 (u8 a, u8 b) { u8 i; i = 4 ^ a; i = i ^ b; USART_SendData(USART3, 0x7e ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, 0x04 ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, a); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, b); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, i); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, 0xef ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); } void MY1690_CMD3 (u8 a, u16 b) { u8 i, c, d; c = b / 0x100 ; d = b % 0x100 ; i = 5 ^ a; i = i ^ c; i = i ^ d; USART_SendData(USART3, 0x7e ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, 0x05 ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, a); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, c); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, d); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, i); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); USART_SendData(USART3, 0xef ); while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" #include "encoder.h" #include "my1690.h" #include "usart.h" int main (void ) { u8 b; u8 MP3 = 0 ; delay_ms(500 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); ENCODER_Init(); MY1690_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(3 , " MP3 Play Test " ); while (1 ) { if (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D) == 0 ) { delay_ms(20 ); if (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0 ) { MY1690_PREV(); OLED_DISPLAY_8x16_BUFFER(6 , " -- PREV -- " ); delay_ms(500 ); OLED_DISPLAY_8x16_BUFFER(6 , " -- PLAY -- " ); } if (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B) == 0 ) { MY1690_NEXT(); OLED_DISPLAY_8x16_BUFFER(6 , " -- NEXT -- " ); delay_ms(500 ); OLED_DISPLAY_8x16_BUFFER(6 , " -- PLAY -- " ); } if (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C) == 0 ) { MY1690_CMD2(0x31 , 30 ); delay_ms(500 ); } if (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D) == 0 ) { MY1690_CMD3(0x41 , 0x04 ); delay_ms(500 ); } while (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D) == 0 ); } b = ENCODER_READ(); if (b == 1 ) { MY1690_VUP(); } if (b == 2 ) { MY1690_VDOWN(); } if (b == 3 ) { if (MP3 == 0 ) { MP3 = 1 ; MY1690_PLAY(); OLED_DISPLAY_8x16_BUFFER(6 , " -- PLAY -- " ); } else if (MP3 == 1 ) { MP3 = 0 ; MY1690_PAUSE(); OLED_DISPLAY_8x16_BUFFER(6 , " -- PAUSE -- " ); } delay_ms(500 ); } if (USART3_RX_STA == 1 ) { MP3 = 0 ; OLED_DISPLAY_8x16_BUFFER(6 , " -- STOP -- " ); USART3_RX_STA = 0 ; } else if (USART3_RX_STA == 2 ) { USART3_RX_STA = 0 ; } } }
时钟语音播报
TF
卡语音播报文件当中,0001 ~ 0011
是阿拉伯数字0 ~ 10
的发音,0012
是现在是北京时间
的发音,0013
是点
的发音,0014
是分
的发音,0015
是秒
的发音。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 #include "delay.h" #include "oled0561.h" #include "relay.h" #include "rtc.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" #include "encoder.h" #include "my1690.h" #include "usart.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RTC_Config(); TOUCH_KEY_Init(); RELAY_Init(); ENCODER_Init(); MY1690_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " MP3 Time Read " ); while (1 ) { if (RTC_Get() == 0 ) { OLED_DISPLAY_8x16(4 , 8 * 3 , rhour / 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 8 * 4 , rhour % 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 8 * 5 , ':' ); OLED_DISPLAY_8x16(4 , 8 * 6 , rmin / 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 8 * 7 , rmin % 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 8 * 8 , ':' ); OLED_DISPLAY_8x16(4 , 8 * 9 , rsec / 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 8 * 10 , rsec % 10 + 0x30 ); delay_ms(200 ); } if (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D) == 0 ) { delay_ms(20 ); if (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0 ) { OLED_DISPLAY_8x16_BUFFER(6 , " -- PLAY -- " ); MY1690_CMD3(0x41 , 12 ); if (rhour / 10 == 0 ) { } else if (rhour / 10 == 1 ) { MY1690_CMD3(0x41 , 11 ); } else { MY1690_CMD3(0x41 , rhour / 10 + 1 ); MY1690_CMD3(0x41 , 11 ); } if (rhour % 10 != 0 || rhour == 0 ) { MY1690_CMD3(0x41 , rhour % 10 + 1 ); } MY1690_CMD3(0x41 , 13 ); if (rmin / 10 == 0 ) { MY1690_CMD3(0x41 , rmin / 10 + 1 ); } else if (rmin / 10 == 1 ) { MY1690_CMD3(0x41 , 11 ); } else { MY1690_CMD3(0x41 , rmin / 10 + 1 ); MY1690_CMD3(0x41 , 11 ); } if (rmin % 10 != 0 || rmin == 0 ) { MY1690_CMD3(0x41 , rmin % 10 + 1 ); } MY1690_CMD3(0x41 , 14 ); if (rsec / 10 == 0 ) { MY1690_CMD3(0x41 , rsec / 10 + 1 ); } else if (rsec / 10 == 1 ) { MY1690_CMD3(0x41 , 11 ); } else { MY1690_CMD3(0x41 , rsec / 10 + 1 ); MY1690_CMD3(0x41 , 11 ); } if (rsec % 10 != 0 || rsec == 0 ) { MY1690_CMD3(0x41 , rsec % 10 + 1 ); } MY1690_CMD3(0x41 , 15 ); } while (GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C) == 0 || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D) == 0 ); } if (USART3_RX_STA == 1 ) { OLED_DISPLAY_8x16_BUFFER(6 , " -- STOP -- " ); USART3_RX_STA = 0 ; } } }
CH376 文件管理控制芯片
CH376T
是江苏沁恒股份有限公司生产的一款文件管理控制芯片,主要用于单片机系统读写
U 盘或者 SD 卡,并且可以通过 SPI 和 UART
两种总线方式与STM32F103C8T6 进行通信,当前实验电路选择的是
SPI 总线连接方式。设置好右上角的跳线坐,短接实验电路右上角的 TF
CARD 、MY1680 、USB
跳线座,并且将stm32f10x_spi
标准外设库添加至当前工程,后续将着手完成一个用于检测
U 盘插入状态以及进行文件系统读取的实验程序。
STM32F103C8T6 拥有 2 个 SPI
接口(SPI1 的PA4/SPI1_NSS
、PA5/SPI1_SCK
、PA6/SPI1_MISO
、PA7/SPI1_MOSI
引脚,SPI2 的PB12/SPI2_NSS
、PB13/SPI2_SCK
、PB14/SPI2_MISO
、PB15/SPI2_MOSI
引脚),每个接口拥有
MISO
(主机接收,从机发送)、MOSI
(主机发送,从机接收)、SCK
(同步时钟)、NSS
(设备选择)
四条信号线,并且所有的主从设备都需要进行共地连接。由于 SPI
总线上的设备没有地址的概念,所以需要通过NSS
信号线的电平状态来选中需要通信的设备(低电平使能,高电平失能)。STM32F103C8T6 只拥有
1 条NSS
信号线,如果需要连接更多的设备,就需要使用到普通的
GPIO 引脚来模拟相关的功能。
实验电路中,电路图里的SPI_CS
、SPI_CLK
、SPI_DI
、SPI_DO
分别代表SPI2 的PB12/SPI2_NSS
、PB13/SPI2_SCK
、PB14/SPI2_MISO
、PB15/SPI2_MOSI
引脚,经过P16 和PA15 跳线座以后,连接到
CH376T
芯片的SCS
、SCK
、SDI
、SDO
引脚。
CH376T 芯片的INT
引脚连接 到 TF 卡实验电路的的 TF
插入状态标志引脚Cd
,SPI
引脚接入到低电平选择使用
SPI
通信,SD_DI
、SD_CK
、SD_DO
、SD_CS
连接至
TF
卡电路,V3
是3.3V
电源引脚,UD+
和UD-
则是连接到
USB
插槽的两条数据线,最后XO
和XI
用于连接一枚外部的12MHz
晶振以及起振电容C20
。
U 盘插拔状态
spi.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef __SPI_H #define __SPI_H #include "sys.h" #define SPI2PORT GPIOB #define SPI2_MOSI GPIO_Pin_15 #define SPI2_MISO GPIO_Pin_14 #define SPI2_SCK GPIO_Pin_13 #define SPI2_NSS GPIO_Pin_12 void SPI2_Init (void ) ; u8 SPI2_SendByte (u8 Byte) ; #endif
spi.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include "spi.h" void SPI2_Init (void ) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); GPIO_InitStructure.GPIO_Pin = SPI2_MISO; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(SPI2PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = SPI2_MOSI | SPI2_SCK; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(SPI2PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = SPI2_NSS; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(SPI2PORT, &GPIO_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7 ; SPI_Init(SPI2, &SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); } u8 SPI2_SendByte (u8 Byte) { while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI2, Byte); while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(SPI2); }
ch376inc.h
1 2 3 4 5 #define CMD01_GET_IC_VER 0x01
ch376.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #ifndef __CH376_H #define __CH376_H #include "ch376inc.h" #include "delay.h" #include "spi.h" #include "sys.h" #define CH376_INTPORT GPIOA #define CH376_INT GPIO_Pin_15 void CH376_PORT_INIT (void ) ;void xEndCH376Cmd (void ) ;void xWriteCH376Cmd (u8 mCmd) ;void xWriteCH376Data (u8 mData) ;u8 xReadCH376Data (void ) ; u8 Query376Interrupt (void ) ; u8 mInitCH376Host (void ) ; #endif
ch376.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include "CH376.h" void CH376_PORT_INIT (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(CH376_INTPORT, CH376_INT); GPIO_SetBits(SPI2PORT, SPI2_NSS); } void xEndCH376Cmd (void ) { GPIO_SetBits(SPI2PORT, SPI2_NSS); } void Spi376OutByte (u8 d) { SPI2_SendByte(d); } u8 Spi376InByte (void ) { while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(SPI2); } void xWriteCH376Cmd (u8 mCmd) { GPIO_SetBits(SPI2PORT, SPI2_NSS); delay_us(20 ); GPIO_ResetBits(SPI2PORT, SPI2_NSS); Spi376OutByte(mCmd); delay_us(1700 ); } void xWriteCH376Data (u8 mData) { Spi376OutByte(mData); delay_us(800 ); } u8 xReadCH376Data (void ) { u8 i; delay_us(10 ); i = SPI2_SendByte(0xFF ); return (i); } u8 Query376Interrupt (void ) { u8 i; i = GPIO_ReadInputDataBit(CH376_INTPORT, CH376_INT); return (i); } u8 mInitCH376Host (void ) { u8 res; delay_ms(600 ); CH376_PORT_INIT(); xWriteCH376Cmd(CMD11_CHECK_EXIST); xWriteCH376Data(0x55 ); res = xReadCH376Data(); xEndCH376Cmd(); if (res != 0xAA ) { return (ERR_USB_UNKNOWN); } xWriteCH376Cmd(CMD11_SET_USB_MODE); xWriteCH376Data(0x06 ); delay_us(20 ); res = xReadCH376Data(); xEndCH376Cmd(); if (res == CMD_RET_SUCCESS) { return (USB_INT_SUCCESS); } else { return (ERR_USB_UNKNOWN); } }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include "ch376.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "spi.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" int main (void ) { u8 s; delay_ms(500 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " U Disk Test " ); SPI2_Init(); if (mInitCH376Host() == USB_INT_SUCCESS) { OLED_DISPLAY_8x16_BUFFER(4 , " CH376 OK! " ); } while (1 ) { s = CH376DiskConnect(); if (s == USB_INT_SUCCESS) { OLED_DISPLAY_8x16_BUFFER(6 , " U Disk Ready! " ); } else { OLED_DISPLAY_8x16_BUFFER(6 , " " ); } delay_ms(500 ); } }
文件系统读写
filesys.h
filesys.c
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include "ch376.h" #include "delay.h" #include "filesys.h" #include "oled0561.h" #include "relay.h" #include "spi.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" #include <stdio.h> #include <string.h> u8 buf[128 ]; int main (void ) { u8 s i; delay_ms(500 ); RCC_Configuration(); TOUCH_KEY_Init(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " U Disk Test " ); SPI2_Init(); if (mInitCH376Host() == USB_INT_SUCCESS) { OLED_DISPLAY_8x16_BUFFER(4 , " CH376 OK! " ); } while (1 ) { while (CH376DiskConnect() != USB_INT_SUCCESS) { delay_ms(100 ); } OLED_DISPLAY_8x16_BUFFER(6 , " U DISK Ready! " ); delay_ms(200 ); for (i = 0 ; i < 100 ; i++) { delay_ms(50 ); s = CH376DiskMount(); if (s == USB_INT_SUCCESS) { break ; } else if (s == ERR_DISK_DISCON) { break ; } if (CH376GetDiskStatus() >= DEF_DISK_MOUNTED && i >= 5 ) { break ; } } OLED_DISPLAY_8x16_BUFFER(6 , " U Disk Init! " ); delay_ms(200 ); s = CH376FileCreatePath("/Hank.txt" ); delay_ms(200 ); s = sprintf ((char *)buf, "Bit by bit:https://uinika.github.io/" ); s = CH376ByteWrite(buf, s, NULL ); delay_ms(200 ); s = CH376FileClose(TRUE); OLED_DISPLAY_8x16_BUFFER(6 , " U DISK SUCCESS " ); while (CH376DiskConnect() == USB_INT_SUCCESS) { delay_ms(500 ); } OLED_DISPLAY_8x16_BUFFER(6 , " " ); delay_ms(200 ); } }
4 × 4 矩阵键盘
将 4 × 4
矩阵键盘连接到STM32F103C8T6 的PA0 ~ PA7
共 4
个 GPIO 引脚,同时断开 ADC
输入相关的P8 跳线座、模拟量摇杆对应的P17 跳线座、触摸按键对应的P10 跳线座、旋转编码器对应的P18 跳线座。
上面的电路图里的红色与蓝色接线,作用类似于地图的经线与纬线,只需要获取按键触发的经纬线,就可以知道当前哪个按键被按下。例如,如果按下【A】键,此时将PA0 ~ PA3
设置为上拉电阻输入模式并置为高电平1
,再将PA4 ~ PA7
设置为推挽输出模式并置为低电平0
,此时按下【A】键让
PA2 与 PA6 导通,此时 PA2 被 PA6 下拉为低电平,即 PA2
对应的【8】【9】【A】【B】当中有按键被按下。
接下来,交换两组 GPIO
的工作模式,即让PA0 ~ PA3
设置为推挽输出模式并置为低电平0
,然后PA4 ~ PA7
设置为上拉电阻输入模式并置为高电平1
,此时按下【A】键会让
PA2 把 PA6 下拉为低电平,由此可知按下【A】键会导致 PA2 和 PA6
分别被下拉为低电平0
,根据矩阵键盘的经纬线原则,就可以定位当前被按下的是【A】键。这种方式的缺点在于,同一时间只允许
1
枚按键被按下,如果发生多个按键同时按下的情况,就不能正确的判断按键位置。
注意: 矩阵键盘直接连接到微控制器会占用大量 GPIO
引脚,因此也可以采用专用的矩阵键盘驱动芯片CH456 (可同时驱动
16 位数码管以及 8×8
矩阵按键)、MAX7300 自动扫描矩阵键盘状态。
KEYPAD4×4.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef __KEYPAD4x4_H #define __KEYPAD4x4_H #include "delay.h" #include "sys.h" #define KEYPAD4x4PORT GPIOA #define KEY1 GPIO_Pin_0 #define KEY2 GPIO_Pin_1 #define KEY3 GPIO_Pin_2 #define KEY4 GPIO_Pin_3 #define KEYa GPIO_Pin_4 #define KEYb GPIO_Pin_5 #define KEYc GPIO_Pin_6 #define KEYd GPIO_Pin_7 void KEYPAD4x4_Init (void ) ; void KEYPAD4x4_Init2 (void ) ; u8 KEYPAD4x4_Read (void ) ; #endif
KEYPAD4×4.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include "KEYPAD4x4.h" void KEYPAD4x4_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = KEYa | KEYb | KEYc | KEYd; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(KEYPAD4x4PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2 | KEY3 | KEY4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(KEYPAD4x4PORT, &GPIO_InitStructure); } void KEYPAD4x4_Init2 (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2 | KEY3 | KEY4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(KEYPAD4x4PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = KEYa | KEYb | KEYc | KEYd; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(KEYPAD4x4PORT, &GPIO_InitStructure); } u8 KEYPAD4x4_Read (void ) { u8 a = 0 , b = 0 ; KEYPAD4x4_Init(); GPIO_ResetBits(KEYPAD4x4PORT, KEY1 | KEY2 | KEY3 | KEY4); GPIO_SetBits(KEYPAD4x4PORT, KEYa | KEYb | KEYc | KEYd); if (!GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYa) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYb) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYc) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYd)) { delay_ms(20 ); if (!GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYa) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYb) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYc) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYd)) { a = GPIO_ReadInputData(KEYPAD4x4PORT) & 0xff ; } KEYPAD4x4_Init2(); GPIO_SetBits(KEYPAD4x4PORT, KEY1 | KEY2 | KEY3 | KEY4); GPIO_ResetBits(KEYPAD4x4PORT, KEYa | KEYb | KEYc | KEYd); b = GPIO_ReadInputData(KEYPAD4x4PORT) & 0xff ; a = a | b; switch (a) { case 0xee : b = 16 ; break ; case 0xed : b = 15 ; break ; case 0xeb : b = 14 ; break ; case 0xe7 : b = 13 ; break ; case 0xde : b = 12 ; break ; case 0xdd : b = 11 ; break ; case 0xdb : b = 10 ; break ; case 0xd7 : b = 9 ; break ; case 0xbe : b = 8 ; break ; case 0xbd : b = 7 ; break ; case 0xbb : b = 6 ; break ; case 0xb7 : b = 5 ; break ; case 0x7e : b = 4 ; break ; case 0x7d : b = 3 ; break ; case 0x7b : b = 2 ; break ; case 0x77 : b = 1 ; break ; default : b = 0 ; break ; } while (!GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEY1) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEY2) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEY3) || !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEY4)); delay_ms(20 ); } return (b); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include "KEYPAD4x4.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 s; delay_ms(500 ); RCC_Configuration(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(3 , " Keyboard Test: " ); KEYPAD4x4_Init(); while (1 ) { s = KEYPAD4x4_Read(); if (s != 0 ) { OLED_DISPLAY_8x16_BUFFER(6 , " Key NO. " ); OLED_DISPLAY_8x16(6 , 8 * 8 , s / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 9 * 8 , s % 10 + 0x30 ); } } }
EXTI 与 NVIC
中断是指一个突然发生的事件打断了当前 MCU
的执行流程,微控制器转而去处理该中断事件,完成之后再重新返回到之前的执行流程。STM32F103C8T6 允许多种类型的中断源,包括外部
GPIO 、ADC 、USART 、I²C 、RTC 、USB 、PVD 等。本小节将要进行分析的
EXTI 外部中断支持将所有 GPIO
设置为中断源,可由上升沿
、下降沿
、高低电平
3
种方式进行触发,并且可以选择中断方式 (触发后进入专门的中断处理函数,需要
MCU 介入)或者事件方式 (触发后自动运行相应功能,毋须
MCU 介入)触发。
下面的表格,整理了STM32F103C8T6 与 EXTI
外部中断相关的所有 GPIO 引脚、中断标志位、中断处理函数:
PA0 ~ PG0
EXTI0
EXTI
0 _IRQHandler()
PA1 ~ PG1
EXTI1
EXTI
1 _IRQHandler()
PA2 ~ PG2
EXTI2
EXTI
2 _IRQHandler()
PA3 ~ PG3
EXTI3
EXTI
3 _IRQHandler()
PA4 ~ PG4
EXTI4
EXTI
4 _IRQHandler()
PA5 ~ PG5
EXTI5
EXTI
9_5 _IRQHandler()
PA6 ~ PG6
EXTI6
EXTI
9_5 _IRQHandler()
PA7 ~ PG7
EXTI7
EXTI
9_5 _IRQHandler()
PA8 ~ PG8
EXTI8
EXTI
9_5 _IRQHandler()
PA9 ~ PG9
EXTI9
EXTI
9_5 _IRQHandler()
PA10 ~ PG10
EXTI10
EXTI
15_10 _IRQHandler()
PA11 ~ PG11
EXTI11
EXTI
15_10 _IRQHandler()
PA12 ~ PG12
EXTI12
EXTI
15_10 _IRQHandler()
PA13 ~ PG13
EXTI13
EXTI
15_10 _IRQHandler()
PA14 ~ PG14
EXTI14
EXTI
15_10 _IRQHandler()
PA15 ~ PG15
EXTI15
EXTI
15_10 _IRQHandler()
NVIC
嵌套中断向量控制器,这里的嵌套 是指中断的嵌套,中断向量 是指中断处理函数的入口地址,即
NVIC 本质上就是 Cortex M3
当中,用于对中断处理函数进行相应设置的硬件单元。NVIC
当中涉及两个比较重要的概念:
抢占式优先级 (主优先级):中断嵌套时,主优先级高的中断可以嵌套在主优先级低的中断当中,相同主优先级的中断不能进行嵌套。
响应式优先级 (子优先级):如果同时发生相同主优先级的中断,STM32F103C8T6 会首先处理子优先级较高的中断。
1 2 3 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2 ; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2 ;
抢占和响应优先级数值的设定范围,由sys.h
当中定义的 NVIC
分组号NVIC_PriorityGroup_x
来进行配置,不同的分组决定了抢占和响应优先级的数量,如果超过分组最大的优先级设定范围,代码可能会在执行过程中发生不可预知的错误,本实验相关的
NVIC 分组号由下面代码进行设置:
1 2 3 4 void NVIC_Configuration (void ) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); }
最后,将前一小节主函数扫描矩阵键盘实验的代码,修改为通过下降沿进行触发的中断来判断,具体代码如下所示:
NVIC.h
1 2 3 4 5 6 7 8 9 #ifndef __NVIC_H #define __NVIC_H #include "sys.h" extern u8 INT_MARK; void KEYPAD4x4_INT_INIT (void ) ; #endif
NVIC.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #include "NVIC.h" u8 INT_MARK; void KEYPAD4x4_INT_INIT (void ) { NVIC_InitTypeDef NVIC_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4); EXTI_InitStruct.EXTI_Line = EXTI_Line4; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2 ; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2 ; NVIC_Init(&NVIC_InitStruct); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5); EXTI_InitStruct.EXTI_Line = EXTI_Line5; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2 ; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2 ; NVIC_Init(&NVIC_InitStruct); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6); EXTI_InitStruct.EXTI_Line = EXTI_Line6; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2 ; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2 ; NVIC_Init(&NVIC_InitStruct); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7); EXTI_InitStruct.EXTI_Line = EXTI_Line7; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2 ; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2 ; NVIC_Init(&NVIC_InitStruct); } void EXTI4_IRQHandler (void ) { if (EXTI_GetITStatus(EXTI_Line4) != RESET) { INT_MARK = 1 ; EXTI_ClearITPendingBit(EXTI_Line4); } } void EXTI9_5_IRQHandler (void ) { if (EXTI_GetITStatus(EXTI_Line5) != RESET) { INT_MARK = 2 ; EXTI_ClearITPendingBit(EXTI_Line5); } if (EXTI_GetITStatus(EXTI_Line6) != RESET) { INT_MARK = 3 ; EXTI_ClearITPendingBit(EXTI_Line6); } if (EXTI_GetITStatus(EXTI_Line7) != RESET) { INT_MARK = 4 ; EXTI_ClearITPendingBit(EXTI_Line7); } }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include "KEYPAD4x4.h" #include "NVIC.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 s; delay_ms(500 ); RCC_Configuration(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(3 , " Keyboard Test: " ); INT_MARK = 0 ; NVIC_Configuration(); KEYPAD4x4_Init(); KEYPAD4x4_INT_INIT(); while (1 ) { if (INT_MARK) { INT_MARK = 0 ; s = KEYPAD4x4_Read(); if (s != 0 ) { OLED_DISPLAY_8x16_BUFFER(6 , " Key NO. " ); OLED_DISPLAY_8x16(6 , 8 * 8 , s / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 9 * 8 , s % 10 + 0x30 ); } } } }
SG90 舵机
SG90
舵机工作电压为5V
,最大工作电流为2A
,可旋转角度为180°
。由于需要使用到触摸按键来进行舵机控制,因此实验开始之前,需要先短接触摸按键相关的P10 跳线座。
上面接线图当中,标号为PWM 的橙色线用于传输脉冲宽度调制信号,标号为VCC 的红色线和标号为GND 的棕色线分别用于表示电源正负极。STM32F103C8T6 对于舵机的控制主要依靠
PWM 信号线来传输连续 的波形。
上面的信号波形示意图当中,如果高电平持续时间长度介于0.5ms ~ 2.5ms
之间,则会让舵盘的转角介于0° ~ 180°
范围以内。
SG90.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef __SG90_H #define __SG90_H #include "delay.h" #include "sys.h" #define SE_PORT GPIOA #define SE_OUT GPIO_Pin_15 void SG90_Init (void ) ; void SG90_angle (u8 a) ; #endif
SG90.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "SG90.h" void SG90_Init (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = SE_OUT; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SE_PORT, &GPIO_InitStructure); GPIO_WriteBit(SE_PORT, SE_OUT, (BitAction)(0 )); } void SG90_angle (u8 a) { u8 b = 100 ; GPIO_WriteBit(SE_PORT, SE_OUT, (BitAction)(1 )); delay_us(500 + a * 10 + b); GPIO_WriteBit(SE_PORT, SE_OUT, (BitAction)(0 )); delay_us(19500 - a * 10 - b); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include "SG90.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(3 , " SG90 Test " ); TOUCH_KEY_Init(); SG90_Init(); SG90_angle(0 ); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { OLED_DISPLAY_8x16_BUFFER(6 , " Angle 0 " ); SG90_angle(0 ); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { OLED_DISPLAY_8x16_BUFFER(6 , " Angle 45 " ); SG90_angle(45 ); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { OLED_DISPLAY_8x16_BUFFER(6 , " Angle 90 " ); SG90_angle(90 ); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { OLED_DISPLAY_8x16_BUFFER(6 , " Angle 180 " ); SG90_angle(180 ); } } }
PWM 脉冲宽度调制
脉冲宽度调制(PWM,P ulse W idth
M odulation)有时也被称为占空比 ,可以通过STM32F103C8T6 的定时器功能(包括
1 个高级定时器TIM1
和 3
个普通定时器TIM2
、TIM3
、TIM4
)来产生
PWM。具体而言,就是借助定时器的TIMx_ARR
寄存器溢出值产生 PWM
周期,然后由TIMx_CCRx
寄存器产生高低电平的占空比。
本实验沿用前一小节通过延时函数驱动舵机的程序与跳线设置,此外为了连接定时器相关的
GPIO
输出,需要使用杜邦线将PB0 和PA15 短接。本实验将会通过触摸按键产生
PWM 信号来控制舵机,由于 PWM
是使用定时器生成的,所以无论用户是否按下触摸按键 PWM
都会一直处于工作状态,此时由于输入电平长度有一定误差,因而按下触摸按键时舵机会产生抖动。另外,在舵机工作的同时,实验电路中核心板附近连接到
PB0 引脚的 LED
指示灯会持续点亮,由于本实验会将PB0 与PA15 短接,因此
LED 也会体现出当前 PWM 生成的信号状态。
pwm.h
1 2 3 4 5 6 7 8 #ifndef __PWM_H #define __PWM_H #include "sys.h" void TIM3_PWM_Init (u16 arr, u16 psc) ;#endif
pwm.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include "pwm.h" void TIM3_PWM_Init (u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStrue; TIM_OCInitTypeDef TIM_OCInitStrue; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrue; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStrue.GPIO_Pin = GPIO_Pin_0; GPIO_InitStrue.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStrue.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStrue); TIM_TimeBaseInitStrue.TIM_Period = arr; TIM_TimeBaseInitStrue.TIM_Prescaler = psc; TIM_TimeBaseInitStrue.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStrue.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStrue); TIM_OCInitStrue.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStrue.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStrue.TIM_OutputState = TIM_OutputState_Enable; TIM_OC3Init(TIM3, &TIM_OCInitStrue); TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include "delay.h" #include "oled0561.h" #include "pwm.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "touch_key.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(3 , " SG90 Test2 " ); TOUCH_KEY_Init(); TIM3_PWM_Init(59999 , 23 ); while (1 ) { if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A)) { OLED_DISPLAY_8x16_BUFFER(6 , " Angle 0 " ); TIM_SetCompare3(TIM3, 1500 ); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B)) { OLED_DISPLAY_8x16_BUFFER(6 , " Angle 45 " ); TIM_SetCompare3(TIM3, 3000 ); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C)) { OLED_DISPLAY_8x16_BUFFER(6 , " Angle 90 " ); TIM_SetCompare3(TIM3, 4500 ); } if (!GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D)) { OLED_DISPLAY_8x16_BUFFER(6 , " Angle 180 " ); TIM_SetCompare3(TIM3, 7500 ); } } }
DHT11 温湿度传感器
DHT11
是一款由广州奥松电子出品的温湿度传感器,其供电电压介于3V ~ 5.5V
之间,温度采集范围为0°C ~ 50°C
,湿度采集范围为20% ~ 90%
,一共拥有
4 个引脚(第 1 脚连接至5V
电源,第 2
脚连接到微控制器的PA15 引脚,第 3 脚悬空,第 4
脚连接至电源负极
GND),采用单总线通信方式与STM32F103C8T6 进行连接。
DHT11
采用简化的串行单总线通信方式,即所有的数据交换与控制都只通过一条数据线完成。单总线结构通常需要外接一枚4.7kΩ
上拉电阻,在总线闲置时将其钳制为高电平状态。STM32F103C8T6 与DHT11 通信时,每次会传送
40 位数据(8
个字节),并且遵循高位先出的顺序,具体数据格式如下所示:
1 8 位湿度整数部分 | 8 位湿度小数部分 | 8 位温度整数部分 | 8 位温度小数部分 | 8 位校验位
注意: 8 位校验位的值等于前面 4 部分累加结果的最后 8
位。进行数据采集时,必须间隔 1 秒以上进行,以确保获得正确的结果。
dht11.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef __DHT11_H #define __DHT11_H #include "delay.h" #include "sys.h" #define DHT11PORT GPIOA #define DHT11_IO GPIO_Pin_15 void DHT11_IO_OUT (void ) ;void DHT11_IO_IN (void ) ;void DHT11_RST (void ) ;u8 Dht11_Check (void ) ; u8 Dht11_ReadBit (void ) ; u8 Dht11_ReadByte (void ) ; u8 DHT11_Init (void ) ; u8 DHT11_ReadData (u8 *h) ; #endif
dht11.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #include "dht11.h" void DHT11_IO_OUT (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_IO; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11PORT, &GPIO_InitStructure); } void DHT11_IO_IN (void ) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_IO; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(DHT11PORT, &GPIO_InitStructure); } void DHT11_RST (void ) { DHT11_IO_OUT(); GPIO_ResetBits(DHT11PORT, DHT11_IO); delay_ms(20 ); GPIO_SetBits(DHT11PORT, DHT11_IO); delay_us(30 ); } u8 Dht11_Check (void ) { u8 retry = 0 ; DHT11_IO_IN(); while (GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO) && retry < 100 ) { retry++; delay_us(1 ); } if (retry >= 100 ) { return 1 ; } else { retry = 0 ; } while (!GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO) && retry < 100 ) { retry++; delay_us(1 ); } if (retry >= 100 ) { return 1 ; } return 0 ; } u8 Dht11_ReadBit (void ) { u8 retry = 0 ; while (GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO) && retry < 100 ) { retry++; delay_us(1 ); } retry = 0 ; while (!GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO) && retry < 100 ) { retry++; delay_us(1 ); } delay_us(40 ); if (GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO)) { return 1 ; } else { return 0 ; } } u8 Dht11_ReadByte (void ) { u8 i, dat; dat = 0 ; for (i = 0 ; i < 8 ; i++) { dat <<= 1 ; dat |= Dht11_ReadBit(); } return dat; } u8 DHT11_Init (void ) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); DHT11_RST(); return Dht11_Check(); } u8 DHT11_ReadData (u8 *h) { u8 buf[5 ]; u8 i; DHT11_RST(); if (Dht11_Check() == 0 ) { for (i = 0 ; i < 5 ; i++) { buf[i] = Dht11_ReadByte(); } if ((buf[0 ] + buf[1 ] + buf[2 ] + buf[3 ]) == buf[4 ]) { *h = buf[0 ]; h++; *h = buf[2 ]; } } else { return 1 ; } return 0 ; }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include "delay.h" #include "dht11.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u8 b[2 ]; delay_ms(1000 ); RCC_Configuration(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Hank " ); OLED_DISPLAY_8x16_BUFFER(2 , " DHT11 Test " ); if (DHT11_Init() == 0 ) { OLED_DISPLAY_8x16_BUFFER(4 , "Humidity: % " ); OLED_DISPLAY_8x16_BUFFER(6 , "Temperature: C" ); } else { OLED_DISPLAY_8x16_BUFFER(4 , " Init Error! " ); } delay_ms(1000 ); while (1 ) { if (DHT11_ReadData(b) == 0 ) { OLED_DISPLAY_8x16(4 , 9 * 8 , b[0 ] / 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 10 * 8 , b[0 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 12 * 8 , b[1 ] / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 13 * 8 , b[1 ] % 10 + 0x30 ); } else { OLED_DISPLAY_8x16_BUFFER(6 , " Read Error! " ); } delay_ms(1000 ); } }
MPU6050 加速度陀螺仪
MPU-6050
整合了三轴加速度和三轴陀螺仪传感器,有效降低了封装尺寸以及两者配合工作时的误差。本小节实验程序将会在
OLED
屏幕左侧的X Y Z
字符后面分别显示三轴加速度(位移)传感值,右侧的X Y Z
字符后面则用于显示三轴陀螺仪(方向)传感值。
将MPU6050 模块的VCC
和GND
引脚分别连接至实验电路的5V
和GND
进行供电,然后再将
I²C
通信相关的SCL
和SDA
分别连接至STM32F103C8T6 的PB6
和PB7
引脚,这样就完成了实验电路的全部连接工作,下面的表格对模块的各引脚进行了详细的功能说明:
VCC
5V
电源正极
GND
GND
电源负极
SCL
PB6
从模式 I²C 时钟线
SDA
PB7
从模式 I²C 数据线
XCL
悬空
主模式 I²C 时钟线
XDA
悬空
主模式 I²C 数据线
AD0
悬空
设备地址,悬空时为低电平
INT
悬空
中断输出,即向微控制器发送中断信号
MPU6050
的设备地址为0xD0
,而官方数据手册上给出的地址是0x68
,由于
I²C 的地址为 7 位,使用 8
位字节的方式进行表达时,会出现两种状况:在低位补0
结果为1101 0000
,在高位补0
结果为0110 1000
,本实验电路使用STM32F103C8T6 的
I²C 外设时,采用的是0xD0
地址。
MPU6050.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #ifndef __MPU6050_H #define __MPU6050_H #include "delay.h" #include "i2c.h" #include "sys.h" #define MPU6050_ADD 0xD0 #define MPU6050_RA_SMPLRT_DIV 0x19 #define MPU6050_RA_CONFIG 0x1A #define MPU6050_RA_GYRO_CONFIG 0x1B #define MPU6050_RA_ACCEL_CONFIG 0x1C #define MPU6050_RA_PWR_MGMT_1 0x6B #define MPU6050_RA_ACCEL_XOUT_H 0x3B #define MPU6050_RA_ACCEL_XOUT_L 0x3C #define MPU6050_RA_ACCEL_YOUT_H 0x3D #define MPU6050_RA_ACCEL_YOUT_L 0x3E #define MPU6050_RA_ACCEL_ZOUT_H 0x3F #define MPU6050_RA_ACCEL_ZOUT_L 0x40 #define MPU6050_RA_TEMP_OUT_H 0x41 #define MPU6050_RA_TEMP_OUT_L 0x42 #define MPU6050_RA_GYRO_XOUT_H 0x43 #define MPU6050_RA_GYRO_XOUT_L 0x44 #define MPU6050_RA_GYRO_YOUT_H 0x45 #define MPU6050_RA_GYRO_YOUT_L 0x46 #define MPU6050_RA_GYRO_ZOUT_H 0x47 #define MPU6050_RA_GYRO_ZOUT_L 0x48 void MPU6050_Init (void ) ; void MPU6050_READ (u16 *n) ; #endif
MPU6050.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include "MPU6050.h" void MPU6050_Init (void ) { I2C_SAND_BYTE(MPU6050_ADD, MPU6050_RA_PWR_MGMT_1, 0x80 ); delay_ms(1000 ); I2C_SAND_BYTE(MPU6050_ADD, MPU6050_RA_PWR_MGMT_1, 0x00 ); I2C_SAND_BYTE(MPU6050_ADD, MPU6050_RA_SMPLRT_DIV, 0x07 ); I2C_SAND_BYTE(MPU6050_ADD, MPU6050_RA_CONFIG, 0x06 ); I2C_SAND_BYTE(MPU6050_ADD, MPU6050_RA_ACCEL_CONFIG, 0x00 ); I2C_SAND_BYTE(MPU6050_ADD, MPU6050_RA_GYRO_CONFIG, 0x18 ); } void MPU6050_READ (u16 *n) { u8 i; u8 t[14 ]; I2C_READ_BUFFER(MPU6050_ADD, MPU6050_RA_ACCEL_XOUT_H, t, 14 ); for (i = 0 ; i < 3 ; i++) { n[i] = ((t[2 * i] << 8 ) + t[2 * i + 1 ]); } for (i = 4 ; i < 7 ; i++) { n[i - 1 ] = ((t[2 * i] << 8 ) + t[2 * i + 1 ]); } }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "MPU6050.h" #include "delay.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u16 t[6 ] = {0 }; delay_ms(500 ); RCC_Configuration(); RELAY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " MPU6050 Test " ); OLED_DISPLAY_8x16_BUFFER(2 , "X: X: " ); OLED_DISPLAY_8x16_BUFFER(4 , "Y: Y: " ); OLED_DISPLAY_8x16_BUFFER(6 , "Z: Z: " ); MPU6050_Init(); while (1 ) { MPU6050_READ(t); OLED_DISPLAY_8x16(2 , 2 * 8 , t[0 ] / 10000 + 0x30 ); OLED_DISPLAY_8x16(2 , 3 * 8 , t[0 ] % 10000 / 1000 + 0x30 ); OLED_DISPLAY_8x16(2 , 4 * 8 , t[0 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(2 , 5 * 8 , t[0 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(2 , 6 * 8 , t[0 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(2 , 11 * 8 , t[3 ] / 10000 + 0x30 ); OLED_DISPLAY_8x16(2 , 12 * 8 , t[3 ] % 10000 / 1000 + 0x30 ); OLED_DISPLAY_8x16(2 , 13 * 8 , t[3 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(2 , 14 * 8 , t[3 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(2 , 15 * 8 , t[3 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 2 * 8 , t[1 ] / 10000 + 0x30 ); OLED_DISPLAY_8x16(4 , 3 * 8 , t[1 ] % 10000 / 1000 + 0x30 ); OLED_DISPLAY_8x16(4 , 4 * 8 , t[1 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(4 , 5 * 8 , t[1 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 6 * 8 , t[1 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 11 * 8 , t[4 ] / 10000 + 0x30 ); OLED_DISPLAY_8x16(4 , 12 * 8 , t[4 ] % 10000 / 1000 + 0x30 ); OLED_DISPLAY_8x16(4 , 13 * 8 , t[4 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(4 , 14 * 8 , t[4 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(4 , 15 * 8 , t[4 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 2 * 8 , t[2 ] / 10000 + 0x30 ); OLED_DISPLAY_8x16(6 , 3 * 8 , t[2 ] % 10000 / 1000 + 0x30 ); OLED_DISPLAY_8x16(6 , 4 * 8 , t[2 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(6 , 5 * 8 , t[2 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 6 * 8 , t[2 ] % 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 11 * 8 , t[5 ] / 10000 + 0x30 ); OLED_DISPLAY_8x16(6 , 12 * 8 , t[5 ] % 10000 / 1000 + 0x30 ); OLED_DISPLAY_8x16(6 , 13 * 8 , t[5 ] % 1000 / 100 + 0x30 ); OLED_DISPLAY_8x16(6 , 14 * 8 , t[5 ] % 100 / 10 + 0x30 ); OLED_DISPLAY_8x16(6 , 15 * 8 , t[5 ] % 10 + 0x30 ); delay_ms(200 ); } }
Low-power Modes 低功耗模式
微控制器的功率是各种片上外设与模块的功率总和,低功耗模式则是通过关闭部分芯片内部的功能,以达到降低功耗节约电能的目的。STM32F103C8T6 拥有睡眠(Sleep) 、停机(Stop) 、待机(Standby) 三种低功耗模式模式,并且支持通过Vʙᴀᴛ
引脚为实时时钟以及备用寄存器供电。
睡眠模式
ARM 内核
所有内、外部功能的中断事件
停机模式
ARM 内核以及内部所有功能、PLL
分频器、HSE
外部中断输入引脚 EXTI、电源电压监控中断
PVD、RTC 时钟、USB 唤醒信号
待机模式
ARM 内核以及内部所有功能、PLL
分频器、HSE、SDRAM
NRST 引脚外部复位、独立看门狗 IWDG
复位、专用唤醒引脚 WKUP、RTC 时钟
参考: STM32F103C8T6
正常模式下工作电流约10mA
,进入睡眠模式后为2mA
,进入停机模式之后仅20uA
,而进入待机模式则只有2uA
,这些数据根据程序和集成电路工艺的不同而略有差异,但是能对低功耗下各模式的能耗状况有一个直观的认识。
当 ARM Cortex M3
内核空闲的时候,可以进入睡眠模式 。由于仅仅只关闭了内核,节能效果有限,因此极少在祼机系统当中使用,但是常用于嵌入式操作系统当中。而进入停机模式 以后,SRAM
里的内容不会消失,程序也不会自动复位,在唤醒以后可以恢复之前的工作状态,其节能效果与待机模式相近,在电池供电的场景下具备一定优势。但是,进入待机模式 以后,SRAM
的数据将会掉电丢失,唤醒后程序会自动复位,由于待机 和停机 两种模式下消耗的电流差别仅以uA
为单位,因此更多场景会采用停机模式 ,而仅在与之前工作状态无关,且运行并不频繁的场景下考虑采用待机模式 。
Sleep 睡眠模式
将下面的程序编译下载运行,实验电路上电STM32F103C8T6 默认进入睡眠模式,但是按下核心板电路上的KEY1 按键,可以通过产生中断唤醒微控制器。当按键弹起后经过0.5
秒左右时间,微控制器又会重新进入睡眠模式。
NVIC.h
1 2 3 4 5 6 7 8 9 #ifndef __NVIC_H #define __NVIC_H #include "sys.h" extern u8 INT_MARK; void KEY_INT_INIT (void ) ; #endif
NVIC.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include "NVIC.h" u8 INT_MARK; void KEY_INT_INIT (void ) { NVIC_InitTypeDef NVIC_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); EXTI_InitStruct.EXTI_Line = EXTI_Line0; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2 ; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2 ; NVIC_Init(&NVIC_InitStruct); } void EXTI0_IRQHandler (void ) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { INT_MARK = 1 ; EXTI_ClearITPendingBit(EXTI_Line0); } }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include "NVIC.h" #include "delay.h" #include "key.h" #include "led.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RELAY_Init(); LED_Init(); KEY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Sleep Test " ); INT_MARK = 0 ; NVIC_Configuration(); KEY_INT_INIT(); NVIC_SystemLPConfig(NVIC_LP_SEVONPEND, DISABLE); NVIC_SystemLPConfig(NVIC_LP_SLEEPDEEP, DISABLE); NVIC_SystemLPConfig(NVIC_LP_SLEEPONEXIT, DISABLE); while (1 ) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); OLED_DISPLAY_8x16_BUFFER(4 , " MCU Sleep! " ); delay_ms(500 ); __WFI(); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0 )); OLED_DISPLAY_8x16_BUFFER(4 , " MCU Wake Up! " ); delay_ms(500 ); } }
Stop 停机模式
将下面的程序编译下载运行,实验电路上电STM32F103C8T6 默认进入停机模式,按下核心板电路上的KEY1 按键,可以通过产生中断唤醒微控制器。当按键弹起0.5
秒以后,微控制器又会重新进入停机模式。
NVIC.h
NVIC.c
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include "NVIC.h" #include "delay.h" #include "key.h" #include "led.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RELAY_Init(); LED_Init(); KEY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Stop Test " ); INT_MARK = 0 ; NVIC_Configuration(); KEY_INT_INIT(); RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); while (1 ) { GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); OLED_DISPLAY_8x16_BUFFER(4 , " MCU Stop! " ); delay_ms(500 ); PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); RCC_Configuration(); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0 )); OLED_DISPLAY_8x16_BUFFER(4 , " MCU Wake Up! " ); delay_ms(500 ); } }
Standby 待机模式
本实验需要断开触摸按键对应PA10 跳线座的PA0 跳线,下面程序下载上电以后,STM32F103C8T6 默认进入待机模式,只能通过实验电路上的【复位
K1】和【唤醒
K2】两个按键进行唤醒。例如:K2 按键一端连接到3.3V
电源,另外一端连接至WKUP/PA0
引脚,按键按下时该引脚会输入一个高电平上升沿信号,从而唤醒微控制器。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include "delay.h" #include "led.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RELAY_Init(); LED_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " Standby Test " ); RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); PWR_WakeUpPinCmd(ENABLE); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0 )); OLED_DISPLAY_8x16_BUFFER(4 , " MCU Reset! " ); delay_ms(500 ); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 )); OLED_DISPLAY_8x16_BUFFER(4 , " Standby! " ); delay_ms(500 ); PWR_EnterSTANDBYMode(); }
WDG 看门狗定时器
看门狗(WDG,Watch
Dog)是STM32F103C8T6 的一个片上外设单元,原理上是一个定时计数器,当其开始递减计数以后,每间隔一段时间微控制器就会发出一条复位指令,从而使其重新开始递减计数;如果运行期间看门狗未进行正确的复位,则递减计数至0
以后,看门狗就会强制复位系统或进行其它指定处理。
IWDG 独立看门狗
独立看门狗(IWDG,Independent Watchdog)基于一个 12
位的递减计数器和一个 8
位的预分频器,并由内部独立于主时钟的40kHz
的 RC
振荡器提供时钟源,可以运行于停机与待机模式之下。独立看门狗可以在计数到0
之前随时进行喂狗操作,主要用于监控程序是否正常运行 。
注意: 编写实验工程时,必须向项目当中加入stm32f10x_iwdg.h
和stm32f10x_iwdg.c
标准函数库文件。
iwdg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef __IWDG_H #define __IWDG_H #include "sys.h" #define pre IWDG_Prescaler_64 #define rlr 625 void IWDG_Init (void ) ; void IWDG_Feed (void ) ; #endif
iwdg.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include "iwdg.h" void IWDG_Init (void ) { IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); IWDG_SetPrescaler(pre); IWDG_SetReload(rlr); IWDG_ReloadCounter(); IWDG_Enable(); } void IWDG_Feed (void ) { IWDG_ReloadCounter(); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include "delay.h" #include "iwdg.h" #include "key.h" #include "led.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RELAY_Init(); LED_Init(); KEY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " IWDG Test " ); OLED_DISPLAY_8x16_BUFFER(4 , " Reset! " ); delay_ms(800 ); OLED_DISPLAY_8x16_BUFFER(4 , " " ); IWDG_Init(); while (1 ) { IWDG_Feed(); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { delay_s(2 ); } } }
WWDG 窗口看门狗
窗口看门狗(WWDG,Window Watchdog)内置一个 7
位递减计数器,并且由主时钟进行驱动,具备早期预警中断功能。窗口看门狗必须在指定的时间范围内进行喂狗操作,主要作用是监控单片机运行时效是否精确 。
注意: 编写实验工程时,必须向项目当中加入stm32f10x_wwdg.h
和stm32f10x_wwdg.c
标准函数库文件。
wwdg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef __WWDG_H #define __WWDG_H #include "sys.h" #define WWDG_CNT 0x7F #define wr 0x50 #define fprer WWDG_Prescaler_8 void WWDG_Init (void ) ; void WWDG_NVIC_Init (void ) ; void WWDG_Feed (void ) ; #endif
wwdg.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include "wwdg.h" void WWDG_Init (void ) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); WWDG_SetPrescaler(fprer); WWDG_SetWindowValue(wr); WWDG_Enable(WWDG_CNT); WWDG_ClearFlag(); WWDG_NVIC_Init(); WWDG_EnableIT(); } void WWDG_NVIC_Init (void ) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void WWDG_Feed (void ) { WWDG_SetCounter(WWDG_CNT); } void WWDG_IRQHandler (void ) { WWDG_ClearFlag(); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include "delay.h" #include "key.h" #include "led.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "wwdg.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RELAY_Init(); LED_Init(); KEY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " WWDG Test " ); OLED_DISPLAY_8x16_BUFFER(4 , " Reset! " ); delay_ms(800 ); OLED_DISPLAY_8x16_BUFFER(4 , " " ); WWDG_Init(); while (1 ) { delay_ms(54 ); WWDG_Feed(); if (!GPIO_ReadInputDataBit(KEYPORT, KEY1)) { delay_s(2 ); } } }
TIM2/3/4 标准定时器
STM32F103C8T6 内置了TIM2 、TIM3 、TIM4 三个可同步运行的标准定时器,每个定时器都有一个
16 位自动加载递加/递减计数器、一个 16 位预分频器、4
个独立的通道,其中每个通道都可用于输入捕获、输出比较、PWM
和单脉冲模式输出。
本小节将会基于TIM3 标准定时器 3
实现了一个让LED1 间隔1
秒进行闪烁的实验,进行实验之前需要先往项目中加入定时器相关的stm32f10x_tim.h
和stm32f10x_tim.c
标准函数库源文件。
tim.h
1 2 3 4 5 6 7 8 9 #ifndef __PWM_H #define __PWM_H #include "sys.h" void TIM3_Init (u16 arr, u16 psc) ; void TIM3_NVIC_Init (void ) ; #endif
tim.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include "led.h" #include "tim.h" void TIM3_Init (u16 arr, u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrue; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM3_NVIC_Init(); TIM_TimeBaseInitStrue.TIM_Period = arr; TIM_TimeBaseInitStrue.TIM_Prescaler = psc; TIM_TimeBaseInitStrue.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStrue.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStrue); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); TIM_Cmd(TIM3, ENABLE); } void TIM3_NVIC_Init (void ) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x3 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x3 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void TIM3_IRQHandler (void ) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 - GPIO_ReadOutputDataBit(LEDPORT, LED1))); } }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "delay.h" #include "key.h" #include "led.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "tim.h" int main (void ) { delay_ms(500 ); RCC_Configuration(); RELAY_Init(); LED_Init(); KEY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " TIM3 Test " ); TIM3_Init(9999 , 7199 ); while (1 ) { } }
CRC 循环冗余校验
STM32F103C8T6 内置的循环冗余校验(CRC,C yclic
R edundancy
C heck)计算单元基于固定的多项式,可以得到任意 32 位的
CRC
计算结果,该结果可以应用于通信与数据存储的校验。在进行实验之前需要向工程当中加入
CRC
校验相关的stm32f10x_crc.h
和stm32f10x_crc.c
标准函数库文件。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include "delay.h" #include "key.h" #include "led.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" int main (void ) { u32 a, b; u8 c; u32 y[3 ] = {0x87654321 , 0x98765432 , 0x09876543 }; delay_ms(500 ); RCC_Configuration(); RELAY_Init(); LED_Init(); KEY_Init(); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " CRC Test " ); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE); while (1 ) { CRC_ResetDR(); CRC_CalcCRC(0x12345678 ); CRC_CalcCRC(0x23456789 ); a = CRC_CalcCRC(0x34567890 ); CRC_ResetDR(); b = CRC_CalcBlockCRC(y, 3 ); CRC_SetIDRegister(0x5a ); c = CRC_GetIDRegister(); } }
芯片 ID 编码
STM32F103C8T6 拥有 96 位芯片 ID
编码,分别固定存放于如下 12 个地址,其中每个地址存放 8
位编码。因此,读取读取该 ID
编码的时候,可以采用8 位方式读取 12 个数据
、16 位方式读取 6 个数据
、32 位方式读取 3 个数据
三种方式。
1 2 0x1FFFF7E8 | 0x1FFFF7E9 | 0x1FFFF7EA | 0x1FFFF7EB | 0x1FFFF7EC | 0x1FFFF7ED | 0x1FFFF7EE | 0x1FFFF7EF | 0x1FFFF7F0 | 0x1FFFF7F1 | 0x1FFFF7F2 | 0x1FFFF7F3
小端数据 存放是指数据整合时,将低位地址 数据放入内存的低位地址 部分;大端数据 存放方式正好反其道而行之,即将将低位地址 数据放入内存的高位地址 部分。ARM
架构微控制器为小端存放方式,而 8051 架构单片机为大端存放方式。
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include "delay.h" #include "key.h" #include "led.h" #include "oled0561.h" #include "relay.h" #include "stm32f10x.h" #include "sys.h" #include "usart.h" int main (void ) { u32 ID[3 ]; delay_ms(500 ); RCC_Configuration(); RELAY_Init(); LED_Init(); KEY_Init(); USART1_Init(115200 ); I2C_Configuration(); OLED0561_Init(); OLED_DISPLAY_8x16_BUFFER(0 , " STM32 ChipID " ); ID[0 ] = *(__IO u32 *)(0X1FFFF7E8 ); ID[1 ] = *(__IO u32 *)(0X1FFFF7EC ); ID[2 ] = *(__IO u32 *)(0X1FFFF7F0 ); printf ("STM32F103C8T6 ChipID: %08X %08X %8X \r\n" , ID[0 ], ID[1 ], ID[2 ]); if (ID[0 ] == 0x0669FF52 && ID[1 ] == 0x52517178 && ID[2 ] == 0x67255837 ) { printf ("ChipID match! \r\n" ); } else { printf ("chipID error! \r\n" ); } while (1 ) {} }