0%

一.实验目标

1.通过编程使用STM32的IO口模拟SPI,控制LCD的显示;

2.通过本实验掌握SPI相关知识和SPI时序;

3.通过本实验掌握STM32的硬件SPI控制外设的基本概念,掌握LCD控制的基本方法。

二.知识储备及设计思路

SPI简介:

SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32F4也有SPI接口。

SPI 通讯设备之间的常用连接方式见图

img

SPI 通讯使用3 条总线及片选线,3 条总线分别为SCK、MOSI、MISO,片选线为SS,它们的作用介绍如下:

(1) SS( Slave Select):从设备选择信号线,常称为片选信号线,也称为NSS、CS,以下

用NSS 表示。当有多个SPI 从设备与SPI 主机相连时,设备的其它信号线SCK、MOSI 及MISO 同时并联到相同的SPI 总线上,即无论有多少个从设备,都共同只使用这3 条总线;而每个从设备都有独立的这一条NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而SPI 协议中没有设备地址,它使用NSS 信号线来寻址,当主机要选择从设备时,把该从设备的NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI 通讯。所以SPI 通讯以NSS 线置低电平为开始信号,以NSS 线被拉高作为结束信号。

(2) SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通

讯的速率,不同的设备支持的最高时钟频率不一样,如STM32 的SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。

(3) MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。

(4) MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

协议层

\1. SPI 基本通讯过程

SPI 通讯的通讯时序:

img

这是一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与MISO 的信号只在NSS 为低电平的时候才有效,在SCK 的每个时钟周期MOSI 和MISO 传输一位数据。

\2. 通讯的起始和停止信号

图的标号1处,NSS 信号线由高变低,是SPI 通讯的起始信号。NSS 是每个从机各自独占的信号线,当从机检在自己的NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。在图中的标号6处,NSS 信号由低变高,是SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

\3. 数据有效性

SPI 使用MOSI 及MISO 信号线来传输数据,使用SCK 信号线进行数据同步。MOSI及MISO 数据线在SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先行或LSB 先行并没有作硬性规定,但要保证两个SPI 通讯设备之间使用同样的协定,一般都会采用图中的MSB 先行模式。

观察图中,MOSI 及MISO 的数据在SCK 的上升沿期间变化输出,在SCK 的下降沿时被采样。即在SCK 的下降沿时刻,MOSI 及MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及MISO为下一次表示数据做准备。SPI 每次数据传输可以8 位或16 位为单位,每次传输的单位数不受限制。

\4. CPOL/CPHA 及通讯模式

上面讲述的图中的时序只是SPI 中的其中一种通讯模式,SPI 一共有四种通讯模式,它们的主要区别是总线空闲时SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入“时钟极性CPOL”和“时钟相位CPHA”的概念。

时钟极性CPOL 是指SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即SPI 通讯开始前、 NSS 线为高电平时SCK 的状态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。

时钟相位CPHA 是指数据的采样的时刻,当CPHA=0 时,MOSI 或MISO 数据线上的信号将会在SCK 时钟线的“奇数边沿‖被采样。当CPHA=1 时,数据线在SCK的“偶数边沿“采样。

img

我们来分析这个CPHA=0 的时序图。首先,根据SCK 在空闲状态时的电平,分为两种情况。SCK 信号线在空闲状态为低电平时,CPOL=0;空闲状态为高电平时,CPOL=1。

无论CPOL=0 还是=1,因为我们配置的时钟相位CPHA=0,在图中可以看到,采样时刻都是在SCK 的奇数边沿。注意当CPOL=0 的时候,时钟的奇数边沿是上升沿,而CPOL=1 的时候,时钟的奇数边沿是下降沿。所以SPI 的采样时刻不是由上升/下降沿决定的。MOSI 和MISO 数据线的有效信号在SCK 的奇数边沿保持不变,数据信号将在SCK 奇数边沿时被采样,在非采样时刻,MOSI 和MISO 的有效信号才发生切换。

STM32 的SPI 特性及架构

STM32 的SPI 外设可用作通讯的主机及从机,支持最高的SCK 时钟频率为fpclk/2(STM32F407 型号的芯片默认fpclk1 为84MHz,fpclk2 为42MHz),完全支持SPI 协议的4 种模式,数据帧长度可设置为8 位或16 位,可设置数据MSB 先行或LSB 先行。它还支持双线全双工(前面小节说明的都是这种模式)、双线单向以及单线模式。

SPI框图:

img

STM32 寄存器简介

SPI 控制寄存器 1 (SPI_CR1)描述如下:

img

img

通过SPI_CR1寄存器我们可以设置波特率,时钟极性,时钟相位,SPI使能等。

SPI 控制寄存器 2 (SPI_CR2)描述如下:

img

通过SPI_CR2寄存器我们可以发送/接收中断使能,DMA发送/接收使能等。

SPI 状态寄存器 (SPI_SR)描述如下:

img

通过SPI_SR寄存器我们可以判断SPI的状态和错误信息等。

SPI 数据寄存器 (SPI_DR)描述如下:

img

通过SPI_SR寄存器我们可以判断SPI的状态和错误信息等。

LCD**简介:**

LCD即薄膜晶体管液晶显示器。其英文全称为:Thin Film Transistor-Liquid Crystal Display。

开发板自带的1.3寸屏幕有如下特点:

1,1.3’的屏幕大小。

2,240×240的分辨率。

3,16位真彩显示(RGB565)。

4,采用四线SPI控制显示

​ 图10.1是屏幕数据手册中的管脚描述:

img

图 10-1 LCD屏幕管脚说明(摘自《1.3寸显示屏规格书》)

LCD屏的控制器是ST7789VW,本章,我们将学习如何通过STM32F429来控制该模块显示字符和数字,本章的实例代码支持的是4线SPI方式。

4线SPI模式使用的信号线有如下几条:

CS:LCD片选信号,常接gnd,拉低。

RESET:硬复位LCD。

D/C:命令/数据标志(0,读写命令;1,读写数据)。

SCL:串行SPI时钟线。

SDA:串行SPI数据线。

在4线SPI模式下,每个数据长度均为8位,在SCLK的上升沿,数据从SDA移入到ST7789VW,并且是高位在前的。DC线是用作命令/数据的标志线。在4线SPI模式下,写操作的时序如图10.2所示:

img

图10.2 4线SPI写操作时序图(摘自《ST7789VW芯片手册》)

最后,我们再来介绍一下LCD模块的初始化过程,ST7789VW的典型初始化框图如图10.3所示:

img

图10.3 初始化框图

驱动IC的初始化代码,我们直接使用厂家推荐的设置就可以了,只要对细节部分进行些

修改,使其满足我们自己的要求即可,其他不需要变动。

LCD的介绍就到此为止,我们重点向大家介绍了屏幕的相关知识,接下来我们将使用这个屏幕来显示字符和数字。通过以上介绍,我们可以得出LCD显示需要的相关设置步骤如下:

1)设置STM32F4LCD模块相连接的IO

这一步,先将我们与LCD模块相连的IO口设置为输出,具体使用哪些IO口,这里需要根据连接电路以及LCD模块所设置的通讯模式来确定。这些将在硬件设计部分向大家介绍。

2)初始化LCD模块。

其实这里就是上面的初始化框图的内容,通过对LCD相关寄存器的初始化,来启动LCD的显示。为后续显示字符和数字做准备。

3)通过函数将字符和数字显示到LCD模块上。

这里就是通过我们设计的程序,将要显示的字符送到LCD模块就可以了,这些函数将在软件设计部分向大家介绍。

三.引脚说明与硬件连接

图10.4为分辨率240*240的LCD屏和硬件连接图。 STM32通过SPI接口控制LCD显示,表10.1为其引脚说明。

img

imgimg

10.4 LCD硬件连接图

10.1 LCD引脚说明

设备名 引脚号
SPI5_SCK PH6
SPI5_MOSI PF9
PF10 PF10
SPI5_MISO PH7
SPI5_NSS PH5

img

10.4 LCD硬件图

四.程序设计

本实验,我们新建了lcd.c , lcd.h , lcd_init.c , lcd_init.h , lcdfont.h ,文件。这五个文件用来存放lcd相关的驱动函数及显示代码。

​ lcd_init.c的代码,由于比较长,这里我们就不贴出来了,仅介绍几个比较重要的函数。LCD_Init函数:

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
void LCD_Init(void)
{
// LCD_GPIO_Init();//初始化GPIO

LCD_RES_Clr();//复位
delay_ms(400);
LCD_RES_Set();
delay_ms(200);

LCD_BLK_Set();//打开背光
delay_ms(100);

//************* Start Initial Sequence **********//
LCD_WR_REG(0x11); //Sleep out
delay_ms(120); //Delay 120ms
//************* Start Initial Sequence **********//
LCD_WR_REG(0x36);
if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x00);
else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC0);
else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x70);
else LCD_WR_DATA8(0xA0);

LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);

//此处省略部分代码,详情请参考实验工程

LCD_WR_REG(0x29);
}

lcd.h的头文件用于外部调用lcd.c的代码,介绍几个比较重要的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

void LCD_Fill(uint16_t xsta,uint16_t ysta,uint16_t xend,uint16_t yend,uint16_t color);//指定区域填充颜色
void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t color);//在指定位置画一个点
void LCD_DrawLine(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2,uint16_t color);//在指定位置画一条线
void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,uint16_t color);//在指定位置画一个矩形
void Draw_Circle(uint16_t x0,uint16_t y0,uint8_t r,uint16_t color);//在指定位置画一个圆

void LCD_ShowChinese(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示汉字串
void LCD_ShowChinese16x16(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示单个16x16汉字
void LCD_ShowChinese24x24(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示单个24x24汉字
void LCD_ShowChinese32x32(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示单个32x32汉字

void LCD_ShowChar(uint16_t x,uint16_t y,uint8_t num,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示一个字符
void LCD_ShowString(uint16_t x,uint16_t y,const uint8_t *p,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示字符串
uint32_t mypow(uint8_t m,uint8_t n);//求幂
void LCD_ShowIntNum(uint16_t x,uint16_t y,uint16_t num,uint8_t len,uint16_t fc,uint16_t bc,uint8_t sizey);//显示整数变量
void LCD_ShowFloatNum1(uint16_t x,uint16_t y,float num,uint8_t len,uint16_t fc,uint16_t bc,uint8_t sizey);//显示两位小数变量

void LCD_ShowPicture(uint16_t x,uint16_t y,uint16_t length,uint16_t width,const uint8_t pic[]);//显示图片

Main.c 函数代码,用于初始话和屏幕显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 MX_GPIO_Init();

/* USER CODE BEGIN 2 */
delay_init(168); //延时初始化
LCD_Init(); //LCD初始化
LCD_Fill(0,0,LCD_W,LCD_H,WHITE); //整个屏幕刷白色
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
LCD_ShowString(0,0," you qing ",RED,WHITE,32,0); //字符串显示函数
LCD_ShowString(30,50,"LCD_W:",RED,WHITE,16,0);
LCD_ShowIntNum(80,50,LCD_W,3,RED,WHITE,16); //整数显示函数
LCD_ShowString(130,50,"LCD_H:",RED,WHITE,16,0);
LCD_ShowIntNum(160,50,LCD_H,3,RED,WHITE,16);
LCD_ShowString(0,80," www.emooc.cc ",RED,WHITE,32,0);
}

对于屏幕如何显示图片和汉字,请自行参照函数来设计。

五.实验结果

将编译好的代码下载到实验板中,按下复位键。

img

六.STM32CubeMX配置硬件SPI

配置IO口时,将SPI模拟的io口配置为高,默认上拉。配置如下:

img

一.实验目标

1.通过编程实现DMA方式实现波形产生;

2.通过DAC1输出频率可调的正弦波。

3.使用TIMER6和DMA1控制DAC输出精确频率正弦波

二.知识储备及设计思路

DMA指直接存储器访问,它可以节省CPU资源,可以将数据从一个地址空间复制到另一个地址空间。这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA
传输对于高效能 嵌入式系统算法和网络是很重要的。

我们通过DAC的思路是:将函数值存储在RAM中,通过DMA将数组值传递给DAC,进而实现波形的生成;对于波形频率的控制可以通过定时器来较为精确的实现,即当定时器发生更新事件时,才执行DAC进行转换,通过控制函数值表转换完的时间进而控制输出波形的频率;对于波形幅度的控制可以对函数值数组进行倍增或缩减。注意DAC要关闭输出缓冲器,否则会产生削波;对于函数值要给一定的直流量,才能正常的输出波形。

DMA 简介

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接
控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O
设备 开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。 STM32F407 最多有 2 个
DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个), 每一个 DMA
控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8
个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
STM32F407 的 DMA 有以下一些特性:

● 双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问

● 仅支持 32 位访问的 AHB 从编程接口

● 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)

● 每个数据流有单独的四级 32 位先进先出存储器缓冲区(FIFO),可用于 FIFO 模式或直
接模式。

● 通过硬件可以将每个数据流配置为:

1,支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道

2,支持在存储器方双缓冲的双缓冲区通道

● 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)

● DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在
软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)

● 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)

● 可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动
DMA 请求

● 要传输的数据项的数目可以由 DMA 控制器或外设管理: 1,DMA
流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程

2外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬
件发出传输结束的信号

● 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA
自动封装/解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用。

● 对源和目标的增量或非增量寻址

● 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等
于外设 FIFO 大小的一半

● 每个数据流都支持循环缓冲区管理

● 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO
错误直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求

STM32F407 有两个 DMA 控制器,DMA1 和 DMA2,本章,我们仅针对 DMA1 进行介绍。
STM32F407的 DMA 控制器框图如图 所示:

DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来
启动 AHB 事务。它可以执行下列事务:

1.外设到存储器的传输

2.存储器到外设的传输

3.存储器到存储器的传输

三.引脚说明与硬件连接

我们在DAC-电压输出的实验基础上进行本实验的开发。打开开发板的原理图,可以看到DAC的引脚是PA4和PA5,分别是通道1和通道2的输出,如图1所示,在使用过程中将这两个管脚接在万用表或者示波器上即可观察DAC两个通道的输出。

图1 单片机内部DAC的输出引脚

图2 开发板上DAC输出脚位置

如图2所示,在开发板上,这两个引脚接在了J2排母上,可以采用杜邦线进行连接。

在实际使用中,我们选择了输出1通道,在实际调试时可以将1通道接在示波器,共地之后就能在示波器上看到DAC的输出波形。

DAC常用的寄存器如下(以DAC1为例):

控制寄存器(CR):

控制寄存器具体介绍如下:

其他寄存器可以在数据手册中查阅,这里仅介绍比较重要的控制寄存器。该寄存器可以配置DAC的使能情况,并配置输出缓冲器,此外还包括DMA、中断等功能的配置,可以通过更改寄存器的值来改变模式。

在HAL库开发模式下,可以用库函数代替寄存器的配置过程。所使用的结构体为:

所使用的主要结构体为DAC_HandleTypeDef,其中主要成员的含义为:

Instance:寄存器基地址

State:DAC通讯状态

……

大部分的定义都是通过对instance成员赋值进行的,具体可参考工程代码。

四.程序整体框图和设计

未命名文件(2)

本实验主要通过DMA的内存-外设功能将函数值表传递给DAC,通过独立按键对幅度和频率进行控制,同时在LCD上显示当前的频率和幅度值。

下面是介绍主要程序内容:

1.DAC配置过程

和DAC实验相比,不同的地方在于本例使用了DMA,故在配置时有所不同。

1
2
3
4
5
| DAC_HandleTypeDef hdac; DMA_HandleTypeDef hdma_dac1;//两个结构体对DMA下的DAC进行配置 |
| ------------------------------------------------------------ |

| |
| ---- |
1
2
3
4
| void MX_DAC_Init(void) {  DAC_ChannelConfTypeDef sConfig = {0};   /\*\* DAC Initialization  \*/  hdac.Instance = DAC;  if (HAL_DAC_Init(&hdac) != HAL_OK)  {  Error_Handler();  }  /\*\* DAC channel OUT1 config  \*/  sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;  if (HAL_DAC_ConfigChannel(&hdac, \&sConfig, DAC_CHANNEL_1) != HAL_OK)  {  Error_Handler();  }  } |
| ------------------------------------------------------------ |

>

这里是CubeMX配置的初始化函数。定义了触发类型为定时器触发,输出缓冲器禁用等。

1
2
3
4
5
| void HAL_DAC_MspInit(DAC_HandleTypeDef\* dacHandle) {  GPIO_InitTypeDef GPIO_InitStruct = {0};  if(dacHandle-\>Instance==DAC)  {  /\* USER CODE BEGIN DAC_MspInit 0 \*/  /\* USER CODE END DAC_MspInit 0 \*/  /\* DAC clock enable \*/  \__HAL_RCC_DAC_CLK_ENABLE();   \__HAL_RCC_GPIOA_CLK_ENABLE();  /\*\*DAC GPIO Configuration  PA4 ------\> DAC_OUT1  \*/  GPIO_InitStruct.Pin = GPIO_PIN_4;  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;  GPIO_InitStruct.Pull = GPIO_NOPULL;  HAL_GPIO_Init(GPIOA, \&GPIO_InitStruct); |
| ------------------------------------------------------------ |

| |
| ---- |
1
2
3
4
| /\* DAC DMA Init \*/  /\* DAC1 Init \*/  hdma_dac1.Instance = DMA1_Stream5;  hdma_dac1.Init.Channel = DMA_CHANNEL_7;  hdma_dac1.Init.Direction = DMA_MEMORY_TO_PERIPH;  hdma_dac1.Init.PeriphInc = DMA_PINC_DISABLE;  hdma_dac1.Init.MemInc = DMA_MINC_ENABLE;  hdma_dac1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;  hdma_dac1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;  hdma_dac1.Init.Mode = DMA_CIRCULAR;  hdma_dac1.Init.Priority = DMA_PRIORITY_MEDIUM;  hdma_dac1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;  if (HAL_DMA_Init(&hdma_dac1) != HAL_OK)  {  Error_Handler();  }  \__HAL_LINKDMA(dacHandle,DMA_Handle1,hdma_dac1);  /\* DAC interrupt Init \*/  HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 0);  HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);  /\* USER CODE BEGIN DAC_MspInit 1 \*/  /\* USER CODE END DAC_MspInit 1 \*/  } } |
| ------------------------------------------------------------ |

>

这一段是对DMA的具体配置。首先对GPIO的PA4即DAC输出引脚进行配置,然后对DMA1的7通道进行使能,这可以在手册中查找到:

可以找到数据流5、通道7即为DAC1的DMA通道。在配置完通道之后,又继续配置了存储器到外设模式,外设地址增量禁止,存储器地址增量使能,传输数据量为单字,进行循环DMA传输。优先级是中等,没有使用输出FIFO。然后将两个结构连接起来,DMA即可给DAC传送数据了。

2.定时器配置过程

1
2
3
4
| void MX_TIM6_Init(void) {  TIM_MasterConfigTypeDef sMasterConfig = {0};   htim6.Instance = TIM6;  htim6.Init.Prescaler = 0;  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;  htim6.Init.Period = 328;  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)  {  Error_Handler();  }  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, \&sMasterConfig) != HAL_OK)  {  Error_Handler();  }  } |
| ------------------------------------------------------------ |

>

这里是CubeMX自动生成的配置代码。具体含义为:将定时器分频系数设置为0(即不进行分频操作,按手册知其时钟为84MHz;计数模式为增加,即从0增加到period项设置的值;period项设置了计数的个数,也即经过多少时钟脉冲后产生一次更新。在计数完成后重新装载,再次计数。

以上是具体的配置代码,其他部分详见工程文件。

五.实验结果

将DAC1输出接入示波器,可以看到有波形输出。按下开发板上的按键对频率和幅度进行调整,可以得到不同状态的波形:

六.STM32CubeMX配置关键硬件截图

本例在上一章节DAC输出程序模板的配置文件上进行开发,所用到的外设如图所示:

其中需要特别配置的有DMA、DAC、TIM6等。下面将分别介绍一下它们的配置细节。

1.DAC

在这里对DMA进行配置,关闭输出缓冲器,触发调整为timer6的更新事件,关闭输出波形模式。在DMA
Setting选项卡里面点击Add,设置循环模式,Word数据长度即可。

2.TIM

在定时器配置中将预分频设为0,计数模式为增加,计数周期为352,自动重载打开,触发事件是更新事件。这里352计算如下:由于时钟频率为84MHz,正弦表长度是256,计数x次后输出正弦表的一个元素,那么我需要1kHz的波形频率时,需要满足(1/84M)*x*256=1ms,计算得x=328.125,近似为328。

3.DMA

对DAC增加DMA配置后,DMA列表自动添加了DMA的配置项。

一.实验目标

1.通过编程使用STM32的硬件IIC,控制EEPROM读写,掌握EEPROM的读写方法;

2.通过本实验掌握STM32的硬件IIC控制外设的基本概念,掌握IIC控制的基本方法。

二.知识储备及设计思路

I2C 通讯协议(Inter-Integrated Circuit)是由Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。

下面我们分别对I2C 协议的物理层及协议层进行讲解。

\1. I2C 物理层

I2C 通讯设备之间的常用连接方式见图 6-1。

img

图 6-1 常见的I2C 通讯系统

它的物理层有如下特点:

(1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个I2C 通讯总线中,可连接多个I2C 通讯设备,支持多个通讯主机及多个通讯从机。

(2) 一个I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。

(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。

(4) 总线通过上拉电阻接到电源。当I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。

(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。

(6) 具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s ,高速模下可达1Mbit/s,但目前大多I2C 设备尚不支持高速模式。

(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

2. 协议层

I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地

址广播等环节。

(1). I2C 基本读写过程

先看看I2C 通讯过程的基本结构,它的通讯过程见图 6-2、图 6-3 及图 6-4。

img

图 6-2 主机写数据到从机

img

图 6-3 主机由从机中读数据

img

图 6-4 I2C 通讯复合格式

img

(2). 通讯的起始和停止信号

前文中提到的起始(S)和停止(P)信号是两种特殊的状态,见图 6-5。当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

img

图 6-5 起始和停止信号

(3). 数据有效性

I2C 使用SDA 信号线来传输数据,使用SCL 信号线进行数据同步。见图 6-6。SDA数据线在SCL 的每个时钟周期传输一位数据。传输时,SCL 为高电平的时候SDA 表示的数据有效,即此时的SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当SCL为低电平时,SDA 的数据无效,一般在这个时候SDA 进行电平切换,为下一次表示数据做好准备。

img

图 6-6 数据有效性

每次数据传输都以字节为单位,每次传输的字节数不受限制。

(4). 地址及数据方向

I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是7 位或10 位,实际中7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第8 位或第11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。见图 6-7。

img

图 6-7 设备地址(7 位)及数据传输方向

读数据方向时,主机会释放对SDA 信号线的控制,由从机控制SDA 信号线,主机接收信号,写数据方向时,SDA 由主机控制,从机接收信号。

(5). 响应

I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。见图 6-8。

img

图 6-8 响应与非响应信号

传输时主机产生时钟,在第9 个时钟时,数据发送端会释放SDA 的控制权,由数据收端控制SDA,若SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。

三.引脚说明与硬件连接

图6.9为EEPROM芯片的硬件连接图。表6.1为其引脚说明。EEPROM存储数据掉电不丢失,EEPROM通过STM32F4的I2C引脚与之通信,实现数据的读写。图1.3.9为EEPROM芯片的硬件连接图。表1.3.8为其引脚说明。EEPROM存储数据掉电不丢失,EEPROM通过STM32F4的I2C引脚与之通信,实现数据的读写。

img

img

6.9 EEPROM芯片硬件连接图

6.1 EEPROM芯片引脚说明

设备名 引脚号
I2C2_SCL PF0
I2C2_SDA PF1

img

图 6-13 EEPROM 管脚说明(摘自《AT24C02》规格书)

本实验板中的EEPROM芯片(型号:AT24C02)的SCL 及SDA 引脚连接到了STM32 对应的I2C 引脚中,结合上拉电阻,构成了I2C 通讯总线,它们通过I2C 总线交互。EEPROM芯片的设备地址一共有7 位,其中高4 位固定为:1010 b,低3 位则由A0/A1/A2信号线的电平决定,见图 6-14,图中的R/W是读写方向位,与地址无关。

img

图 6-14 EEPROM 设备地址(摘自《AT24C02》规格书)

按照我们此处的连接,A0/A1/A2 均为0,所以EEPROM的7 位设备地址是:1010000b ,即0x50。由于I2C 通讯时常常是地址跟读写方向连在一起构成一个8 位数,且当R/W位为0 时,表示写方向,所以加上7 位地址,其值为“0xA0”,常称该值为I2C 设备的“写地址”;当R/W位为1 时,表示读方向,加上7 位地址,其值为“0xA1”,常称该值为“读地址”。

EEPROM芯片中还有一个WP 引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时,可写入数据,我们直接接地,不使用写保护功能。

四.程序设计

本实验,我们新建了24cxx.c , 24cxx.h文件。这两个个文件用来存放EEPROM相关的驱动函数代码;i2c.c和i2c.h是通过使用cube工具配置后生成的代码。

Cube生成的部分I2C的初始化函数.

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
I2C_HandleTypeDef hi2c2;

void MX_I2C2_Init(void)
{
hi2c2.Instance = I2C2;
hi2c2.Init.ClockSpeed = 100000;
hi2c2.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c2.Init.OwnAddress1 = 0;
hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c2.Init.OwnAddress2 = 0;
hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c2) != HAL_OK)
{
Error_Handler();
}
}

void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(i2cHandle->Instance==I2C2)
{
__HAL_RCC_GPIOF_CLK_ENABLE();
/**I2C2 GPIO Configuration
PF0 ------> I2C2_SDA
PF1 ------> I2C2_SCL */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C2;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

__HAL_RCC_I2C2_CLK_ENABLE();
}
}

void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle)
{
if(i2cHandle->Instance==I2C2)
{
__HAL_RCC_I2C2_CLK_DISABLE();
/**I2C2 GPIO Configuration
PF0 ------> I2C2_SDA
PF1 ------> I2C2_SCL
*/
HAL_GPIO_DeInit(GPIOF, GPIO_PIN_0|GPIO_PIN_1);
}
}

熟悉STM32 I2C 结构的话,这段初始化程序就十分好理解了,指定连接EEPROM的I2C是I2C2,地址设置为7bit 模式,关闭双地址模式,禁止通用广播模式,禁止时钟延长模式。最后调用库函数HAL_I2C_Init 把这些配置写入寄存器。

初始化好I2C 外设后,就可以使用I2C 通讯了,我们看看如何向EEPROM读写数据. 24cxx.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
#include "24CXX/24cxx.h"
#include "stm32f4xx_hal.h"
#include "i2c.h"
#include "DELAY/delay.h"
//////////////////////////////////////////////////////////////////////////////////
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
// STM32F4xx开发板
//24CXX驱动代码
//All rights reserved
//////////////////////////////////////////////////////////////////////////////////

I2C_HandleTypeDef *at24cxx = &hi2c2;

uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr)
{
uint8_t temp=0;
uint8_t addr_temp=0;

HAL_I2C_Mem_Read(at24cxx,AT24CXX_READ,ReadAddr%256,I2C_MEMADD_SIZE_8BIT,&temp,1,20);

return temp;
}
//在AT24CXX指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite)
{
uint8_t addr_temp=0;

HAL_I2C_Mem_Write(at24cxx,AT24CXX_WRITE,WriteAddr%256,1,&DataToWrite,1,20);

delay_ms(10);
}

//在AT24CXX里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据.
//WriteAddr :开始写入的地址
//DataToWrite:数据数组首地址
//Len :要写入数据的长度2,4
void AT24CXX_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len)
{
uint8_t t;
for(t=0;t<Len;t++)
{
AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
}
}

//在AT24CXX里面的指定地址开始读出长度为Len的数据
//该函数用于读出16bit或者32bit的数据.
//ReadAddr :开始读出的地址
//返回值 :数据
//Len :要读出数据的长度2,4
uint32_t AT24CXX_ReadLenByte(uint16_t ReadAddr,uint8_t Len)
{
uint8_t t;
uint32_t temp=0;
for(t=0;t<Len;t++)
{
temp<<=8;
temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
}
return temp;
}
//检查AT24CXX是否正常
//这里用了24XX的最后一个地址(255)来存储标志字.
//如果用其他24C系列,这个地址要修改
//返回1:检测失败
//返回0:检测成功
uint8_t AT24CXX_Check(void)
{
uint8_t temp;
temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX
if(temp==0X55)return 0;
else//排除第一次初始化的情况
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
}
return 1;
}

//在AT24CXX里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
//在AT24CXX里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(uint16_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}

这里我们通过调用库函数HAL_I2C_Mem_Write 就可以实现写,HAL_I2C_Mem_Read可以实现读功能,通过封装一次使用更方便。

头文件24cxx.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 _24CXX_H
#define _24CXX_H
//#include "sys.h"
#include "stdint.h"
//////////////////////////////////////////////////////////////////////////////////
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//STM32F4xx开发板
//All rights reserved
//////////////////////////////////////////////////////////////////////////////////

#define AT24C01 127
#define AT24C02 255
#define AT24C04 511
#define AT24C08 1023
#define AT24C16 2047
#define AT24C32 4095
#define AT24C64 8191
#define AT24C128 16383
#define AT24C256 32767
//STM32F4xx开发板使用的是24c02,所以定义EE_TYPE为AT24C02
#define EE_TYPE AT24C02

#define AT24CXX_ADDR 0x50
#define AT24CXX_WRITE 0xA0
#define AT24CXX_READ 0xA1

uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr); //指定地址读取一个字节
void AT24CXX_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite); //指定地址写入一个字节
void AT24CXX_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len);//指定地址开始写入指定长度的数据
uint32_t AT24CXX_ReadLenByte(uint16_t ReadAddr,uint8_t Len); //指定地址开始读取指定长度数据
void AT24CXX_Write(uint16_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite); //从指定地址开始写入指定长度的数据
void AT24CXX_Read(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead); //从指定地址开始读出指定长度的数据

uint8_t AT24CXX_Check(void); //检查器件
#endif


EEPROM 的单字节写入

在这个通讯过程中,STM32 实际上通过I2C 向EEPROM 发送了两个数据,但为何第

一个数据被解释为EEPROM 的内存地址?这是由EEPROM 的自己定义的单字节写入时序。

img

图 6-15 EEPROM 单字节写入时序(摘自《AT24C02》规格书)

EEPROM的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个

字节是要写入的数据内容。所以我们需要理解:命令、地址的本质都是数据,对数据的解

释不同,它就有了不同的功能。

EEPROM 的页写入

在以上的数据通讯中,每写入一个数据都需要向EEPROM发送写入的地址,我们希望

向连续地址写入多个数据的时候,只要告诉EEPROM 第一个内存地址address1,后面的数

据按次序写入到address2、address3… 这样可以节省通讯的内容,加快速度。为应对这种

需求,EEPROM定义了一种页写入时序。

img

图 6-16 EEPROM 页写入时序(摘自《AT24C02》规格书)

根据页写入时序,第一个数据被解释为要写入的内存地址address1,后续可连续发送n 个

数据,这些数据会依次写入到内存中。其中AT24C02 型号的芯片页写入时序最多可以一次

发送8 个数据(即n = 8 ),该值也称为页大小,某些型号的芯片每个页写入时序最多可传输

16 个数据.

EEPROM 读取数据

从EEPROM读取数据是一个复合的I2C 时序,它实际上包含一个写过程和一个读过程,见图 6-17。

img

图 6-17 EEPROM 数据读取时序

读时序的第一个通讯过程中,使用I2C 发送设备地址寻址(写方向),接着发送要读取的“内存地址”;第二个通讯过程中,再次使用I2C 发送设备地址寻址,但这个时候的数据方向是读方向;在这个过程之后,EEPROM会向主机返回从“内存地址”开始的数据,一个字节一个字节地传输,只要主机的响应为“应答信号”,它就会一直传输下去,主机想结束传输时,就发送“非应答信号”,并以“停止信号”结束通讯,作为从机的EEPROM也会停止传输。HAL 库已经帮我们实现了这一个过程.

完成基本的读写函数后,接下来我们编写一个读写测试函数来检验驱动程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//检查AT24CXX是否正常
//这里用了24XX的最后一个地址(255)来存储标志字.
//如果用其他24C系列,这个地址要修改
//返回1:检测失败
//返回0:检测成功
uint8_t AT24CXX_Check(void)
{
uint8_t temp;
temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX
if(temp==0X55)return 0;
else//排除第一次初始化的情况
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
}
return 1;
}

main 函数

编写main 函数,函数中初始化了系统时钟、SPI、LCD屏幕、I2C 外设,然后调用上

面的AT24CXX_Check 函数进行读写测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C2_Init();
delay_init(168);//延时初始化
delay_ms(100);
AT24CXX_Init();//eeprom初始化

while(AT24CXX_Check())//eeprom自检测,通过返回0,不通过返回1
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6, GPIO_PIN_SET);//LED常高
delay_ms(500);
}

while (1)
{
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_6);//LED闪烁
delay_ms(500);
}
}

五.实验结果

将编译好的代码下载到实验板中,按下复位键。观察屏led是常量还是闪烁,如果常亮,代表eeprom初始化出错,如果led闪烁,代表eeprom初始化通过。

六.STM32CubeMX配置硬件I2C

经过前面多个章节的学习,大家对STM32CubeMX配置已经非常熟悉. ,出于篇幅考虑,我们将不再像之前章节一样讲解那么详细,我们将只会列出配置的关键点,然后生成工程,大家自行与提供的实验代码对照学习.

I2C2配置非常简单。配置如下:

img

一.实验目标

1.通过编程使用STM32的timer,产生PWM波;

2.通过按键外部中断的方式控制蜂鸣器发出声响。

3.熟悉timer用作pwm输出时基本操作。。

二.知识储备及设计思路

蜂鸣器简介:

蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。蜂鸣器分为有源蜂鸣器和无源蜂鸣器。这里的有源是指有没有自带震荡电路,有源蜂鸣器自带了震荡电路,一通电就会发声;无源蜂鸣器则没有自带震荡电路,必须外部提供1~5Khz左右的方波驱动,才能发声。

开发板板载的蜂鸣器是无源蜂鸣器,需要外部提供方波才能够响。如图所示是板载无源蜂鸣器:

前面我们已经对STM32F4的IO做了简单介绍,前面章节,我们就是利用STM32的IO口直接驱动LED的,本章的蜂鸣器,我们能否直接用STM32的IO口驱动呢?让我们来分析下:STM32F4的单个IO最大可以提供25mA电流(来自数据手册),而蜂鸣器的驱动电流是30mA左右,两者十分相近,但是全盘考虑,STM32F4整个芯片的电流,最大也就150mA,如果用IO口直接驱动蜂鸣器,其他地方用电就得省着点了…所以,我们不用STM32F4的IO直接驱动蜂鸣器,而是通过三极管扩流后再驱动蜂鸣器,这样STM32F4的IO只需要提供不到1mA的电流就足够了。

TIMER简介

定时器(Timer)最基本的功能就是定时了,比如定时发送USART
数据、定时采集AD数据等等。如果把定时器与GPIO
结合起来使用的话可以实现非常丰富的功能,可以测量输入信号的脉冲宽度,可以生产输出波形。定时器生成PWM控制电机状态是工业控制普遍方法。

STM32F4xxx 系列控制器有2 个高级控制定时器、10 个通用定时器和2
个基本定时器,还有2
个看门狗定时器。看门狗定时器不在本章讨论范围,有专门讲解的章节。控制器上所有定时器都是彼此独立的,不共享任何资源。

定时器类 型 Timer 计数器 分辨率 计数器类 型 预分频系数 DMA请 求生成 捕获/ 比较 通道 互补 输出 最大接口时 钟(MHz) 最大定时器 时钟(MHz)
高级控制 TIM1 和 TIM8 16 位 递增、递 减、递增/ 递减 1~65536(整数) 4 84 (APB2) 168
通用 TIM2, TIM5 递增、递 减、递增/ 递减 1~65536(整数) 4 42 (APB1) 84/168
TIM3, TIM4 16 位 递增、递 减、递增/ 递减 1~65536(整数) 4 42 (APB1) 84/168
TIM9 16 位 递增 1~65536(整数) 2 84 (APB2) 168
TIM10, TIM11 16 位 递增 1~65536(整数) 1 84 (APB2) 168
TIM12 16 位 递增 1~65536(整数) 2 42 (APB1) 84/168
TIM13, TIM14 16 位 递增 1~65536(整数) 1 42 (APB1) 84/168
基本 TIM6 和 TIM7 16 位 递增 1~65536(整数) 0 42 (APB1) 84/168

本节中我们使用的是TIM13,我们就介绍TIM13定时器。

通用定时器主要特性:

● 16 位自动重载递增计数器

● 16 位可编程预分频器,用于对计数器时钟频率进行分频(即运行时修改),分频系数
介于 1 和 65536 之间

● 独立通道,可用于:

— 输入捕获

— 输出比较

— PWM 生成(边沿对齐模式)

— 单脉冲模式输出

● 发生如下事件时生成中断:

— 更新:计数器上溢、计数器初始化(通过软件)

— 输入捕获

— 输出比较

通用定时器框图如图:

基本定时器功能框图

本章节我们用定时器实现PWM功能,需要用到timer模块再框图中标注出。

1.时钟源

定时器要实现计数必须有个时钟源,基本定时器时钟只能来自内部时钟,高级控制定时器和通用定时器还可以选择外部时钟源或者直接来自其他定时器等待模式。我们可以通过RCC
专用时钟配置寄存器(RCC_DCKCFGR)的TIMPRE
位设置所有定时器的时钟频率,我们一般设置该位为默认值0,使得定时器(Timer)最基本的功能就是定时了,比如定时发送USART
数据、定时采集AD 数据等等。

STM32F4xxx 系列控制器有2 个高级控制定时器、10 个通用定时器和2
个基本定时器,还有2
个看门狗定时器。看门狗定时器不在本章讨论范围,有专门讲解的章节。控制器上所有定时器都是彼此独立的,不共享任何资源。

基本定时器只能使用内部时钟,当TIM13控制寄存器1(TIMx_CR1)的CEN 位置1
时,启动基本定时器,并且预分频器的时钟来源就是CK_INT。对于高级控制定时器和通用定时器的时钟源可以来找控制器外部时钟、其他定时器等等模式,较为复杂,我们在相关章节会详细介绍。

表中timer13可选的最大定时器时钟为42MHz,即基本定时器的内部时钟(CK_INT)频率为42MHz。

2.控制器

定时器控制器控制实现定时器功能,控制定时器复位、使能、计数是其基础功能。

3.计数器

基本定时器计数过程主要涉及到三个寄存器内容,分别是计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR),这三个寄存器都是16
位有效数字,即可设置值为0 至65535。

预分频器PSC,它有一个输入时钟CK_PSC 和一个输出时钟CK_CNT。输入时钟CK_PSC
来源于控制器部分,基本定时器只有内部时钟源所以CK_PSC
实际等于CK_INT,即90MHz。在不同应用场所,经常需要不同的定时频率,通过设置预分频器PSC
的值可以非常方便得到不同的CK_CNT,实际计算为:fCK_CNT
等于fCK_PSC/(PSC[15:0]+1)。

下图是将预分频器PSC 的值从1 改为4 时计数器时钟变化过程。原来是1 分频,CK_PSC
和CK_CNT 频率相同。向TIMx_PSC 寄存器写入新值时,并不会马上更新CK_CNT
输出频率,而是等到更新事件发生时,把TIMx_PSC
寄存器值更新到影子寄存器中,使其真正产生效果。更新为4 分频后,在CK_PSC 连续出现4
个脉冲后CK_CNT 才产生一个脉冲。

在定时器使能(CEN 置1)时,计数器COUNTER 根据CK_CNT 频率向上计数,即每来一个CK_CNT
脉冲,TIMx_CNT 值就加1。当TIMx_CNT 值与TIMx_ARR
的设定值相等时就自动生成事件并TIMx_CNT
自动清零,然后自动重新开始计数,如此重复以上过程。为此可见,我们只要设置CK_PSC
和TIMx_ARR
这两个寄存器的值就可以控制事件生成的时间,而我们一般的应用程序就是在事件生成的回调函数中运行的。在TIMx_CNT
递增至与TIMx_ARR 值相等,我们叫做为定时器上溢。

自动重载寄存器TIMx_ARR
用来存放于计数器值比较的数值,如果两个数值相等就生成事件,将相关事件标志位置位,生成DMA
和中断输出。TIMx_ARR 有影子寄存器,可以通过TIMx_CR1 寄存器的ARPE
位控制影子寄存器功能,如果ARPE
位置1,影子寄存器有效,只有在事件更新时才把TIMx_ARR 值赋给影子寄存器。如果ARPE
位为0,修改TIMx_ARR 值马上有效。

经过上面分析,我们知道定时事件生成时间主要由TIMx_PSC 和TIMx_ARR
两个寄存器值决定,这个也就是定时器的周期。比如我们需要一个1ms
周期的定时器,具体这两个寄存器值该如何设置内。假设,我们先设置TIMx_ARR
寄存器值为999,即当TIMx_CNT从0 开始计算,刚好等于999 时生成事件,总共计数1000
次,那么如果此时时钟源周期为1us 即可得到刚好1ms 的定时周期。

接下来问题就是设置TIMx_PSC 寄存器值使得CK_CNT 输出为1us
周期(1000000Hz)的时钟。预分频器的输入时钟CK_PSC
为42MHz,所以设置预分频器值为(42-1)即可满足。

4.捕获/比较

本章节使用定时器的PWM输出功能,下图就是一个简单的PWM原理示意图。

图中,我们假定定时器工作在向上计数PWM模式,且当CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。

STM32F4的定时器除了TIM6和7。其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出!这里我们仅使用TIM13的CH1产生一路PWM输出。

要使STM32F4的通用定时器TIMx产生PWM输出,除了通用介绍的寄存器外,我们还会用到3个寄存器,来控制PWM的。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。接下来我们简单介绍一下这三个寄存器。

STM32 寄存器简介

首先是捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx _CCMR1和TIMx
_CCMR2,描述如下:

这里我们需要说明的是模式设置位OC1M,此部分由3位组成。总共可以配置成7种模式,我们使用的是PWM模式,所以这3位必须设置为110/111。这两种PWM模式的区别就是输出电平的极性相反。另外CC1S用于设置通道的方向(输入/输出)默认设置为0,就是设置通道作为输出使用。

TIMx_CCER捕获/比较使能寄存器:

该寄存器比较简单,我们这里只用到了CC1E位,该位是输入/捕获1输出使能位,要想PWM从IO口输出,这个位必须设置为1,所以我们需要设置该位为1。

捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有4个,对应4个通道CH1~4:

在输出模式下,该寄存器的值与CNT的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制PWM的输出脉宽了。

HAL库函数和cube生成函数介绍

通过以上一些寄存器的操作外加一下IO口的配置,我们就可以达到pwm最基本的配置了,关于timer13更详细的介绍,请参考《STM32F4xx中文参考手册》第445页至483页。接下来我们将着重讲解使用HAL库实现pwm配置和使用的方法。在HAL库中,timer相关的函数和定义主要在文件stm32f4xx_hal_tim.c和stm32f4xx_hal_tim.h中。接下来我们看看HAL库提供的串口相关操作函数。

  1. 通用timer参数初始化(预分频/从装值等)。

串口作为STM32的一个外设,HAL库为其配置了串口初始化函数。接下来我们看看使用CUBE生成的timer初始化函数MX_TIM13_Init

1
void MX_TIM13_Init (void) 

其中用到了初始化函数HAL_SPI_Init相关知识,定义如下:

1
HAL_StatusTypeDef HAL_TIM_Base\_Init(TIM_HandleTypeDef \*htim); 

该函数只有一个入口参数htim,为TIM_HandleTypeDef结构体指针类型,我们俗称其为timer句柄,它的使用会贯穿整个timer程序。一般情况下,我们会定义一个TIM_HandleTypeDef结构体类型全局变量,然后初始化各个成员变量。接下来我们看看结构体TIM_HandleTypeDef的定义:

1
2
3
4
5
6
7
8
9
10
typedef struct
{
TIM_TypeDef *Instance; /*!< Register base address */
TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters */
HAL_TIM_ActiveChannel Channel; /*!< Active channel */
DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_TIM_StateTypeDef State; /*!< TIM operation state */
} TIM_HandleTypeDef;

该结构体成员变量非常多,一般情况下载调用函数HAL_SPI_Init对timer进行初始化的时候,我们只需要先设置Instance和Init两个成员变量的值。接下来我们依次解释一下各个成员变量的含义。

Instance是TIM_TypeDef结构体指针类型变量,它是执行寄存器基地址,实际上这个基地址HAL库已经定义好了,如果是TIM13,取值为TIM13即可。

Init是TIM_Base_InitTypeDef结构体类型变量,它是用来设置TIMER的各个参数,包括预分频系数,自动从装值等,它的使用方法非常简单。

TIM_TypeDef结构体定义如下:

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
typedef struct
{
__IO uint32_t CR1;
__IO uint32_t CR2;
__IO uint32_t SMCR;
__IO uint32_t DIER;
__IO uint32_t SR;
__IO uint32_t EGR;
__IO uint32_t CCMR1;
__IO uint32_t CCMR2;
__IO uint32_t CCER;
__IO uint32_t CNT;
__IO uint32_t PSC;
__IO uint32_t ARR;
__IO uint32_t RCR;
__IO uint32_t CCR1;
__IO uint32_t CCR2;
__IO uint32_t CCR3;
__IO uint32_t CCR4;
__IO uint32_t BDTR;
__IO uint32_t DCR;
__IO uint32_t DMAR;
__IO uint32_t OR;
} TIM_TypeDef;

详细的TIM_TypeDef可以查看寄存器描述和数据手册。

2)timer中PWM输出初始化

我们要使输出PWM,前面已经对通用寄存器初始话,还需要输出pwm相关寄存器初始化,

其中用到了初始化函数HAL_TIM_PWM_ConfigChannel相关知识,定义如下:

1
HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef *sConfig, uint32_t Channel);

该函数第一个入口参数选择timer,第二个是pwm配置,第三个是pwm输出通道接下来我们看看结构体TIM_OC_InitTypeDef的定义:

1
2
3
4
5
6
7
8
9
10
typedef struct
{
uint32_t OCMode;
uint32_t Pulse;
uint32_t OCPolarity;
uint32_t OCNPolarity;
uint32_t OCFastMode;
uint32_t OCIdleState;
uint32_t OCNIdleState;
} TIM_OC_InitTypeDef;

该结构体一般情况下载调用函数HAL_TIM_PWM_ConfigChannel对timer进行初始化的时候,我们一般只需要先设置OCMode和Pulse两个成员变量的值。Pulse就是比较器的值,范围0x0000
- 0xFFFF。

详细的TIM_OC_InitTypeDef可以查看寄存器描述和数据手册。

3)timer13初始化

Timer初始化,调用MX_TIM13_Init,具体方法如下:

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
TIM_HandleTypeDef htim13;

void MX_TIM13_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0};

htim13.Instance = TIM13;
htim13.Init.Prescaler = 167;
htim13.Init.CounterMode = TIM_COUNTERMODE_UP;
htim13.Init.Period = 1000;
htim13.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim13.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim13) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim13) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim13, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim13);

}

其中MX_TIM13_Init函数会调用初始化函数HAL_TIM_Base_MspInit,配置使用:

1
2
3
4
5
6
7
8
9
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM13)
{
__HAL_RCC_TIM13_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 3, 3);
HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
}
}

其中MX_TIM13_Init函数会调用初始化函数HAL_TIM_MspPostInit,使能gpio配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(timHandle->Instance==TIM13)
{
__HAL_RCC_GPIOF_CLK_ENABLE();
/**TIM13 GPIO Configuration PF8 ------> TIM13_CH1 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF9_TIM13;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
}
}

4) timer开启关闭

STM32F4的开启/关闭timer时,调用如下函数就能够实现开启/关闭timer:

1
2
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *htim);

三.引脚说明与硬件连接

图1.3.8为无源蜂鸣器的硬件连接图。通过STM32F4向其对应引脚输出低频的PWM波,可以使其发声。表1.3.7为其引脚说明。

1.3.8 蜂鸣器硬件连接图

1.3.7 蜂鸣器引脚说明

设备名 引脚号
BEEP PF8

在单片机管脚输出1khz的方波,无源蜂鸣器就能够发出“滴滴”响声。

四.程序设计

本实验,在main函数中添加了少部分代码,主要实现开启pwm1,和按键控制蜂鸣器开启/关闭:

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
int main(void)
{
uint8_t key1_value=0,key2_value=0; //按键状态值
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM13_Init();
HAL_TIM_PWM_Start(&htim13,TIM_CHANNEL_1);//开启PWM通道1
while (1)
{
//按键控制LED灯点亮
key1_value=HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_6);
key2_value=HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_7);
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6|GPIO_PIN_7, key2_value);
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10|GPIO_PIN_11, key1_value);
//按键1控制蜂鸣器
if(key1_value == GPIO_PIN_RESET)
{
HAL_TIM_Base_MspInit(&htim13);
}
else
{
HAL_TIM_Base_MspDeInit(&htim13);
}
}
}

五.实验结果

按下按键KEY18,蜂鸣器会发出“滴滴”声,松开按键,蜂鸣器不发声。

六.STM32CubeMX配置硬件timer13

经过前面多个章节的学习,大家对STM32CubeMX配置已经非常熟悉.
,出于篇幅考虑,我们将不再像之前章节一样讲解那么详细,我们将只会列出配置的关键点,然后生成工程,大家自行与提供的实验代码对照学习.

Tim13配置配置如下:

一.实验目标

1.熟悉通过IO口模拟IIC实现方式;

2.通过本实验掌握STM32的模拟IIC控制,熟悉CH455控制。

二.知识储备及设计思路

前面章节详细介绍了I2C的物理层和协议层,通过编程硬件I2C实现EEPRAM的读写,在实际使用中,可能设备硬件I2C接口不够使用,我们就需要通过普通IO口实现模拟I2C功能。

用软件模拟IIC,最大的好处就是方便移植,同一个代码兼容所有MCU,任何一个单片机只要有IO口,就可以很快的移植过去,而且不需要特定的IO口。而硬件IIC,则换一款MCU,基本上就得重新搞一次,移植是比较麻烦的。

CH455芯片简介

CH455 是数码管显示驱动和键盘扫描控制芯片。CH455 内置时钟振荡电路,可以动态驱动 4
位数 码管或者 32 只 LED;同时还可以进行 28 键的键盘扫描;CH455 通过 SCL 和 SDA
组成的 2 线串行接口 与单片机等交换数据。

芯片特点:

● 内置显示电流驱动级,段电流不小于 25mA,字电流不小于 160mA。

● 动态显示扫描控制,支持 8×4 或者 7×4,直接驱动 4 位数码管或者 32 只发光管 LED。

● 内部限流,通过占空比设定提供 8 级亮度控制。

● 内置 28 键键盘控制器,基于 7×4 矩阵键盘扫描。

● 内置按键状态输入的下拉电阻,内置去抖动电路。

● 提供低电平有效的键盘中断,提供按键释放标志位,可供查询按键按下与释放。

● 高速 2 线串行接口,时钟速度从 0 到 4MHz,兼容两线 I 2 C 总线,节约引脚。

● 内置上电复位,支持 2.7V~5V 电源电压。

● 支持低功耗睡眠,节约电能,可以被按键唤醒或者被命令操作唤醒。

● 内置时钟振荡电路,不需要外部提供时钟或者外接振荡元器件,更抗干扰。

● 提供 DIP18、SOP18 和 SOP16 三种无铅封装,功能和引脚部分兼容 CH450 芯片。

CH455功能说明

1) 显示驱动

CH455 对数码管和发光管采用动态扫描驱动,顺序为 DIG0 至 DIG3,段驱动引脚
SEG6~SEG0 分别对应数码管的段 G~段 A,段驱动引脚 SEG7 对应数码管的小数点,字驱
动引脚 DIG3~DIG0 分别连接 4 个数码管的阴极;CH455
将分配给每个数码管的显示驱动时间进一步细分为 8 等份,通过设定显示占空比支持 8 级
亮度控制。占空比的值从 1/8 至
8/8,占空比越大,数码管的平均驱动电流越大,显示亮度也就越高.

CH455 内部具有 4 个 8 位的数据寄存器,用于保存 4 个字数据,分别对应于 CH455
所驱动的 4 个 数码管或者 4 组每组 8 个的发光二极管。

2) 键盘扫描

CH455 的键盘扫描功能支持 7×4 矩阵的 28 键键盘。在键盘扫描期间,DIG3~DIG0
引脚用于列 扫描输出,SEG6~SEG0 引脚都带有内部下拉电阻,用于行扫描输入。CH455
定期在显示驱动扫描过程中插入键盘扫描。

当有键被按下时,例如连接 DIG1 与 SEG4 的键被按下,则 当 DIG1 输出高电平时 SEG4
检测到高电平;为了防止因为按键抖动或者外界干扰而产生误码,CH455
实行两次扫描,只有当两次键盘扫描的结果相同时,按键才会被确认有效。如果 CH455
检测到有效的 按键,则记录下该按键代码,并通过
INT#引脚产生低电平有效的键盘中断,此时单片机可以通过串行接口读取按键代码。

CH455 所提供的按键代码为 8 位:位 7 始终为 0,位 2 始终为 1,位 1~位 0
是列扫描码,位 5~ 位 3 是行扫描码,位 6 是状态码(键按下为 1,键释放为
0)。例如,连接 DIG1 与 SEG4 的键被按下, 则按键代码是 01100101B 或者
65H,键被释放后,按键代码通常是 00100101B 或者 25H(也可能是其它值,但是肯定小于
40H),其中,对应 DIG1 的列扫描码为 01B,对应 SEG4 的行扫描码为 100B。单
片机可以在任何时候读取按键代码,但一般在 CH455
检测到有效按键而产生键盘中断时读取按键代 码,此时按键代码的位 6 总是
1,另外,如果需要了解按键何时释放,单片机可以通过查询方式定期
读取按键代码,直到按键代码的位 6 为 0。

下表是在 DIG3~DIG0 与 SEG6~SEG0 之间 7×4 矩阵的按键按下后编址。

编址 DIG3 DIG2 DIG1 DIG0
SEG0 47H 46H 45H 44H
SEG1 4FH 4EH 4DH 4CH
SEG2 57H 56H 55H 54H
SEG3 5FH 5EH 5DH 5CH
SEG4 67H 66H 65H 64H
SEG5 6FH 6EH 6DH 6CH
SEG6 77H 76H 75H 74H
SEG0+SEG1 7FH 7EH 7DH 7CH

3) 串行接口

CH455 具有硬件实现的 2 线串行接口,包含 2 个主要信号线:串行数据时钟输入线
SCL、串行数 据输入和输出线 SDA;以及 1 个辅助信号线:中断输出线
INT#,默认高电平。

写操作包括以下 6 个步骤:输出启动信号、输出字节 1、应答 1、输出字节 2、应答
2、输出停 止信号。其中,启动信号和停止信号如上所述,应答 1 和应答 2 总是固定为
1,输出字节 1 和输出字 节 2 各自包含 8 个数据位,即一个字节数据。

读操作包括以下 6 个步骤:输出启动信号、输出字节 1、应答 1、输入字节 2、应答
2、输出停 止信号。其中,启动信号和停止信号如上所述,应答 1 和应答 2 总是固定为
1,输出字节 1 和输入字 节 2 各自包含 8 个数据位,即一个字节数据。

下图是一个写操作的实例,字节 1 为 01001000B,即 48H;字节 2 为 00000001B,即
01H。

4) 操作命令

a. 设置系统参数命令

设置系统参数命令用于设定 CH455 的系统级参数:显示及键盘扫描使能 ENA、睡眠使能
SLEEP、7 段模式 7SEG、显示亮度控制 INTENS。该命令不影响内部数据缓冲区中的数据。

该命令的字节 1 为 01001000B,即 48H;输出字节 2 为 0 [INTENS] [7SEG]
[SLEEP]0[ENA]B。

Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
输出字节2 0 [INTENS] [7SEG] [SLEEP] 0 [ENA]

ENA 位为 1 时允许显示输出和键盘扫描,当 ENA 位为 0 时关闭显示驱动和键盘扫描。

SLEEP 位为 1 时使 CH455 进入低功耗睡眠状态,从而可以节约电能。

7SEG 位为 1 时对应 7 段模式,显示扫描为 7×4, 为 0 时对应 8 段模式,显示扫描为
8×4。

INTENS 通过 3 位数据控制,数据 001B~111B 和 000B 设定显示驱动占空比为 1/8~
7/8 和 8/8,默认值是 8/8。

b. 加载字数据命令

该命令的输出字节 1 为地址 68H、6AH、6CH 或者 6EH,分别对应于 DIG0~DIG3
引脚驱动的 4 个 数码管;输出字节 2 为[DIG_DATA]B,即 00H 到 0FFH 之间的值,是 8
位的字数据。

c. 读取按键代码命令

读取按键代码命令用于获得 CH455
最近检测到的有效按键的按键代码。该命令属于读操作,是 唯一的具有数据返回的命令,

该命令的输出字节 1 为 01001111B,即 4FH;输入字节 2 为按键代码

三.引脚说明与硬件连接

图9.1为4位七段共阴极数码管COM1-COM4的硬件连接图。STM32通过IIC接口配置CH455H寄存器,CH455H芯片控制数码管显示和按键扫描。每位数码管由对应的位选信号DIG1-DIG4控制是否选中,低电平有效,即输出低电平则选中该位数码管;选中的七段共阴极数码管由七个字码段SEG0-SEG6以及小数点位SEG7控制是否点亮,高电平有效,即输出高电平对应的引脚点亮。IIC接口9.1为其引脚说明。

9.1 数码管硬件连接图

9.1 数码管引脚说明

设备名 引脚号
CH455_SCL PC14
CH455_SDA PC15
CH455_INT PC13

CH455通过模拟IIC和单片机通信,当按键按下后,CH455_INT管脚会产生一个下降沿,寄存器数据会发生变化,单片机通过检测PC13管脚的边沿,进入中断,在中断中读取IIC数据。

四.程序设计

本实验,我们新建了myiic.c , myiic.h文件用于实现模拟I2C的功能, ch455.c ,
ch455.h实现ch455的控制。

myiic.c的代码如下,用于实现模拟IIC功能.

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
#include "IIC/myiic.h"
#include "DELAY/delay.h"
//////////////////////////////////////////////////////////////////////////////////
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
// STM32F407开发板
//IIC驱动代码
//All rights reserved
//////////////////////////////////////////////////////////////////////////////////

//IIC初始化
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;

__HAL_RCC_GPIOC_CLK_ENABLE(); //使能GPIOC时钟

//PC14,15初始化设置
GPIO_Initure.Pin=GPIO_PIN_14|GPIO_PIN_15;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //快速
HAL_GPIO_Init(GPIOC,&GPIO_Initure);

IIC_SDA_1;
IIC_SCL_1;
}

//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA_1;
IIC_SCL_1;
delay_us(1);
IIC_SDA_0;//START:when CLK is high,DATA change form high to low
delay_us(1);
IIC_SCL_0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL_0;
IIC_SDA_0;//STOP:when CLK is high DATA change form low to high
delay_us(1);
IIC_SCL_1;
delay_us(1);
IIC_SDA_1;//发送I2C总线结束信号
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA_1;delay_us(1);
IIC_SCL_1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL_0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL_0;
SDA_OUT();
IIC_SDA_0;
delay_us(1);
IIC_SCL_1;
delay_us(1);
IIC_SCL_0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL_0;
SDA_OUT();
IIC_SDA_1;
delay_us(1);
IIC_SCL_1;
delay_us(1);
IIC_SCL_0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL_0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if((txd&0x80)>>7 == 1)
{
IIC_SDA_1;
}
else
{
IIC_SDA_0;
}
txd<<=1;
IIC_SCL_1;
delay_us(1);
IIC_SCL_0;
delay_us(1);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
uint8_t IIC_Read_Byte(uint8_t ack)
{
uint8_t i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL_0;
delay_us(1);
IIC_SCL_1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}




该部分为IIC驱动代码,实现包括IIC的初始化(IO口)、IIC开始、IIC结束、ACK、IIC读写等功能,在其他函数里面,只需要调用相关的IIC函数就可以和外部IIC器件通信了,这里并不局限于ch455,该段代码可以用在任何IIC设备上。

打开myiic.h头文件可以看到,我们除了函数申明之外,还定义了几个宏定义标识符:

myiic.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
#ifndef _MYIIC_H
#define _MYIIC_H
#include "stdint.h"
#include "stm32f4xx.h"
//////////////////////////////////////////////////////////////////////////////////
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//All rights reserved
//////////////////////////////////////////////////////////////////////////////////
//IO方向设置
#define SDA_IN() {GPIOC->MODER&=~(uint32_t)(3U<<(15*2));GPIOC->MODER|=(uint32_t)(0U<<15*2);} //PC15输入模式
#define SDA_OUT() {GPIOC->MODER&=~(uint32_t)(3U<<(15*2));GPIOC->MODER|=(uint32_t)(1U<<15*2);} //PC15输出模式


#define IIC_SCL_0 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_RESET) //SCL
#define IIC_SCL_1 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_SET) //SCL

#define IIC_SDA_0 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15,GPIO_PIN_RESET) //SDA
#define IIC_SDA_1 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15,GPIO_PIN_SET) //SDA

#define READ_SDA HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_15) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(uint8_t txd); //IIC发送一个字节
uint8_t IIC_Read_Byte(uint8_t ack);//IIC读取一个字节
uint8_t IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号

void IIC_Write_One_Byte(uint8_t daddr,uint8_t addr,uint8_t data);
uint8_t IIC_Read_One_Byte(uint8_t daddr,uint8_t addr);
#endif


该部分代码的SDA_IN
()和SDA_OUT()分别用于设置IIC_SDA接口为输入和输出,如果这两句代码看不懂,请好好温习下IO口的使用相关寄存器。

本节,外部中断和模拟IIC都在前面章节详细讲解过,这里不再详细描述。最要讲CH455实现读写操作,再将数据写在数码管上显示。

这里我们在main函数调用cube生成的初始化函数MX_GPIO_Init();初始化GPIO参数配置和中断配置,具体函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
  GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_14|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 3, 3);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

接下来就是编写中断回调函数HAL_GPIO_EXTI_Callback。具体函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 按键中断回调函数,在中断环境中执行
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_13)
{
// 判断中断来自于PC13管脚
CH455_KEY_RX_FLAG = 1;
CH455_KEY_NUM = CH455_Key_Read();
__HAL_GPIO_EXTI_CLEAR_FLAG(GPIO_PIN_13);
}
}

回调函数首先判断中断是来自哪个管脚,然后将CH455_KEY_RX_FLAG置1,在中断里通过函数CH455_Key_Read()读取寄存器的值返回给CH455_KEY_NUM。再清除寄存器中断标志位,表示可以接收下一次中断。

再main函数中,在while循环内部,只需一直判断CH455_KEY_RX_FLAG值,如果为1,就按键值显示在数码管上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(void)
{

while (1)
{
if(CH455_KEY_RX_FLAG == 1) //键盘按下后,进入程序读取CH455的值
{
CH455_Display(1,CH455_KEY_NUM);//数码管显示按键的值
CH455_Display(2,CH455_KEY_NUM);
CH455_Display(3,CH455_KEY_NUM);
CH455_Display(4,CH455_KEY_NUM);
CH455_KEY_RX_FLAG = 0;
}
}
}

Ch455.c文件中,我们只部分函数分析。Ch455.h定义了部分寄存器和设定值。

芯片初始化函数CH455_init,用于初始化芯片寄存器,这段程序必须在主函数初始化时中调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
//CH455初始化
void CH455_init(void)
{
uint8_t i;
for ( i = 0; i < 6; i ++ ) CH450_buf_index( i, 0 ); // 因为CH450复位时不清空显示内容,所以刚开电后必须人为清空,再开显示
CH450_buf_write(CH450_SYSON2); // 开启显示及键盘
// 如果需要定期刷新显示内容,那么只要执行7个命令,包括6个数据加载命令,以及1个开启显示命令
CH455_Display(1,1); // 显示BCD码1
CH455_Display(2,2);
CH455_Display(3,3);
CH455_Display(4,4);
}

数码管显示函数CH455_Display,用于4位数码管显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/数码管显示
void CH455_Display(uint8_t digital,uint8_t num)
{
if(digital == 1){
CH450_buf_write(CH450_DIG4 | BCD_decode_tab[num]); //第1位数码管显示
}
else if(digital == 2){
CH450_buf_write(CH450_DIG5 | BCD_decode_tab[num]); //第2位数码管显示
}
else if(digital == 3){
CH450_buf_write(CH450_DIG6 | BCD_decode_tab[num]); //第3位数码管显示
}
else if(digital == 4){
CH450_buf_write(CH450_DIG7 | BCD_decode_tab[num]); //第4位数码管显示
}
}

按键检测函数CH455_Display,读取按键的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
uint8_t CH455_Key_Read(void)		// 向CH450发出按键读操作命令
{
uint8_t keycode;
uint8_t ch455_key_num=0;
IIC_Start();
IIC_Send_Byte(((uint8_t)(CH450_GET_KEY>>7)&CH450_I2C_MASK)|0x01|CH450_I2C_ADDR1);
IIC_Wait_Ack();
keycode=IIC_Read_Byte(0);
IIC_Stop();

//将码值转换为key1-key16
switch(keycode)
{
case 0x44:
ch455_key_num = 0; //对应按键的编号key1
break;
case 0x45:
ch455_key_num = 1;
break;
//省略部分代码
}
return(ch455_key_num);
}

五.实验结果

我们把程序下载到STM32F407开发板,未进行任何操作时,可以看到数码管上显示值是1234,当我们按下按键后,可以看到数码管显示的值发生变化,按键和数码管显示的值对应关系:(key1-key16)
对应 (0 – F)。

六.STM32CubeMx配置

使用CUBE配置GPIO管脚和中断截图图下。

一.实验目标

  1. 编程控制D4-D7四盏LED灯间隔1s左右自右向左顺次点亮,实现流水灯效果;

  2. 通过本实验掌握Keil
    5新建工程、使用STM32库函数编写应用程序、将程序下载至实验板的步骤、方法;

  3. 通过本实验掌握STM32的GPIO外设的基本概念,以及对其输出进行简单操作的基本方法。

二.知识储备及设计思路

在配置GPIO口时,我们有必要了解GPIO的功能和特性。

STM32F407通用GPIO简介

每个通用 I/O 端口包括 4 个 32位配置寄存器(GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR 和 GPIOx_PUPDR)、2 个 32位数据寄存器(GPIOx_IDR 和 GPIOx_ODR)、1 个 32 位置位/复位寄存器(GPIOx_BSRR)、1 个 32 位锁定寄存器 (GPIOx_LCKR) 和 2 个 32位复用功能选择寄存器(GPIOx_AFRH 和GPIOx_AFRL)。

GPIO 主要特性

● 受控 I/O 多达 16 个

● 输出状态:推挽或开漏 + 上拉/下拉

● 从输出数据寄存器 (GPIOx_ODR) 或外设(复用功能输出)输出数据

● 可为每个 I/O 选择不同的速度

● 输入状态:浮空、上拉/下拉、模拟

● 将数据输入到输入数据寄存器 (GPIOx_IDR) 或外设(复用功能输入)

● 置位和复位寄存器 (GPIOx_BSRR),对 GPIOx_ODR 具有按位写权限

● 锁定机制 (GPIOx_LCKR),可冻结 I/O 配置

● 模拟功能

● 复用功能输入/输出选择寄存器(一个 I/O 最多可具有 16 个复用功能)

● 快速翻转,每次翻转最快只需要两个时钟周期

● 引脚复用非常灵活,允许将 I/O 引脚用作 GPIO 或多种外设功能中的一种

GPIO 功能描述

根据数据手册中列出的每个 I/O 端口的特性,可通过软件将通用 I/O (GPIO)
端口的各个端口 位分别配置为多种模式:

● 输入浮空

● 输入上拉

● 输入下拉

● 模拟功能

● 具有上拉或下拉功能的开漏输出

● 具有上拉或下拉功能的推挽输出

● 具有上拉或下拉功能的复用功能推挽

● 具有上拉或下拉功能的复用功能开漏

5V容忍I/O端口基本结构:

每个 I/O 端口位均可自由编程,但 I/O 端口寄存器必须按 32
位字、半字或字节进行访问。 GPIOx_BSRR 寄存器旨在实现对 GPIO ODR
寄存器进行原子读取/修改访问。这样便可确保
在读取和修改访问之间发生中断请求也不会有问题。

接下来我们详细介绍IO配置常用的8个寄存器:
MODER、OTYPER、OSPEEDR、PUPDR、ODR、IDR
、AFRH和AFRL。同时讲解对应的HAL库配置方法。

首先看MODER寄存器,该寄存器是GPIO端口模式控制寄存器,用于控制GPIOx(STM32F4最多有9组IO,分别用大写字母表示,即x=A/B/C/D/E/F/G/H/I,下同)的工作模式,该寄存器各位描述如表所示:

该寄存器各位在复位后,一般都是0(个别不是0,比如JTAG占用的几个IO口),也就是默认条件下一般是输入状态的。每组IO下有16个IO口,该寄存器共32位,每2个位控制1个IO。

OTYPER寄存器,用于控制GPIOx的输出类型,该寄存器各位描述如表所示:

该寄存器仅用于输出模式,在输入模式(MODER[1:0]=00/11时)下不起作用。该寄存器低16位有效,每一个位控制一个IO口。设置为0是推挽输出,设置为1是开漏输出。复位后,该寄存器值均为0,也就是在输出模式下IO口默认为推挽输出。

OSPEEDR寄存器,该寄存器用于控制GPIOx的输出速度,该寄存器各位描述见表:

该寄存器也仅用于输出模式,在输入模式(MODER[1:0]=00/11时)下不起作用。该寄存器每2个位控制一个IO口,复位后,该寄存器值一般为0。

PUPDR寄存器,该寄存器用于控制GPIOx的上拉/下拉,该寄存器各位描述见表:

该寄存器每2个位控制一个IO口,用于设置上下拉,这里提醒大家,STM32F1是通过ODR寄存器控制上下拉的,而STM32F4则由单独的寄存器PUPDR控制上下拉,使用起来更加灵活。复位后,该寄存器值一般为0。

ODR寄存器,该寄存器用于控制GPIOx的输出电平,该寄存器各位描述见表:

接下来我们看看另一个非常重要的寄存器BSRR,它叫置位/复位寄存器。该寄存器和ODR寄存器具有类似的作用,都可以用来设置GPIO端口的输出位是1还是0。该寄存器各位描述见表:

对于低16位(0-15),我们往相应的位写1,那么对应的IO口会输出高电平,往相应的位写0,对IO口没有任何影响。高16位(16-31)作用刚好相反,对相应的位写1会输出低电平,写0没有任何影响。也就是说,对于BSRR寄存器,你写0的话,对IO口电平是没有任何影响的。我们要设置某个IO口电平,只需要相关位设置为1即可。而ODR寄存器,我们要设置某个IO口电平,我们首先需要读出来ODR寄存器的值,然后对整个ODR寄存器重新赋值来达到设置某个或者某些IO口的目的,而BSRR寄存器,我们就不需要先读,而是直接设置即可,这在多任务实时操作系统中作用很大。

我们通过提供的操作函数HAL_GPIO_WritePin能够配置IO.

void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);

IDR寄存器,该寄存器用于读取GPIOx的输入数据,该寄存器各位描述见表:

该寄存器用于读取某个IO的电平,如果对应的位为0(IDRy=0),则说明该IO输入的是低电平,如果是1(IDRy=1),则表示输入的是高电平。HAL库操作该寄存器读取IO输入数据相关函数:

GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

复用功能选择寄存器(AFRH
和AFRL),这两个寄存器是用来设置IO口的复用功能的。实际上,在我们调用函数HAL_GPIO_Init的时候,如果我们设置了初始化结构体成员变量Mode为复用模式,同时设置了Alternate的值,那么会在该函数内部自动设置这两个寄存器的值,达到设置端口复用映射的目的。

GPIO相关的函数和定义分布在HAL库文件stm32f4xx_hal_gpio.c和头文件stm32f4xx_hal_gpio.h文件中。

在这里用到了HAL_GPIO_Init函数,用于配置GPIO口,为了用户使用更加清晰,我们这里分析。

void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);

该函数有两个参数,第一个参数是用来指定需要初始化的GPIO对应的GPIO组,取值范围为GPIOA~GPIOK。第二个参数为初始化参数结构体指针,结构体类型为GPIO_InitTypeDef。下面我们分别看看这两个结构体的定义。

在这个地方用到GPIO_TypeDef结构体,用于定义GPIO的寄存器,我们前面进行初始话设定的值,实际通过函数HAL_GPIO_Init写入这些寄存器中。

typedef struct { __IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */ __IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */ __IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */ __IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */ __IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */ __IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */ __IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */ __IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */ __IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */ } GPIO_TypeDef;

在这个地方用到GPIO_InitTypeDef结构体,用于定义GPIO功能,我们前面进行初始话设定的值,实际通过函数HAL_GPIO_Init写入这些寄存器中。

typedef struct { uint32_t Pin; /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */ uint32_t Mode; /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIO_mode_define */ uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.This parameter can be a value of @ref GPIO_pull_define */ uint32_t Speed; /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIO_speed_define */ uint32_t Alternate; /*!< Peripheral to be connected to the selected pins. This parameter can be a value of @ref GPIO_Alternate_function_selection */ }GPIO_InitTypeDef;

三.引脚说明与硬件连接


3.1.1 LED灯硬件连接图

图3.1.1为LED灯硬件连接图。其中LED灯D4-D5分别连接至GPIOF的6、7引脚,D6-D7连接至GPIOI的10、11引脚。通过配置这4个引脚并输出高低电平,来驱动对应的LED灯并控制其亮灭。

LED灯为高电平驱动点亮,通过控制相对应的GPIO口分别输出高(输出“1”)、低(输出“0”)电平便可控制相对应的LED灯实现点亮、熄灭,因此,流水灯的实现便是数据“1”的“流动”。

四.程序设计

要实现四盏LED灯自右向左的流水灯效果,则需将LED灯按照“0001”这样一组数据,每经过一段延时,整体进行一次循环左移,并通过相对应的GPIO口输出LED灯的引脚状态,从而显示以实现流水灯效果。

在主函数main的while循环中,我们只进行IO高低电平切换。

while (1) { HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6, GPIO_PIN_SET);//PF6设置为高电平 HAL_GPIO_WritePin(GPIOF, GPIO_PIN_7, GPIO_PIN_RESET);//PF7设置为低电平 HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_RESET);//PI10设置为低电平 HAL_GPIO_WritePin(GPIOI, GPIO_PIN_11, GPIO_PIN_RESET);//PI11设置为低电平 HAL_Delay(1000); //HAL库自带延时函数,延时ms HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_7, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOI, GPIO_PIN_11, GPIO_PIN_RESET); HAL_Delay(1000); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_7, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOI, GPIO_PIN_11, GPIO_PIN_RESET); HAL_Delay(1000); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_7, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOI, GPIO_PIN_11, GPIO_PIN_SET); HAL_Delay(1000); }

main函数代码中while(1)循环中代码。其中,代码执行顺序:

控制PF6管脚输出高电平,控制其亮;

控制PF7/PI10/PI11管脚输出低电平,控制其灭;

经过HAL_Delay延时函数1s的延时,然后进入下一状态,继续循环,从而实现流水灯效果。

接下来,将依次介绍main函数中几个主要子函数的功能与内容。

子函数MX_GPIO_Init代码,是stm32cube自动生成的代码,其功能为对LED灯对应的GPIO引脚进行初始化配置。其中,代码分别为:

使能对应GPIO的时钟。

拉低对应的各个引脚使LED灯全部熄灭,准备新的引脚状态的写入,点亮流水灯。

配置对应引脚为输出模式,输出状态为推挽输出,输出速度为低速,无上拉或下拉,并通过函数HAL_GPIO_Init完成配置。

void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOI_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10|GPIO_PIN_11, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET); /*Configure GPIO pins : PI10 PI11 */ GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOI, &GPIO_InitStruct); /*Configure GPIO pins : PF6 PF7 */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOF, &GPIO_InitStruct); }

函数HAL_Delay延时是通过SysTick—系统定时器实现的. SysTick属于CM4
内核中的一个外设,内嵌在NVIC 中。系统定时器是一个24bit
的向下递减的计数器,计数器每计数一次的时间为1/SYSCLK,一般我们设置系统时钟SYSCLK
等于168M。当重装载数值寄存器的值递减到0
的时候,系统定时器就产生一次中断,以此循环往复。

因为SysTick 是属于CM4 内核的外设,所以所有基于CM4 内核的单片机都具有这个

系统定时器,使得软件在CM4 单片机中可以很容易的移植。系统定时器一般用于操作系统,

用于产生时基,维持操作系统的心跳。

HAL_Delay函数输入延时时间,然后通过HAL_GetTick()记录当前tickstart时间,就一致停在while循环,直到(HAL_GetTick()
- tickstart) < wait 达到延时时间,退出while循环。

__weak void HAL_Delay(uint32_t Delay) { uint32_t tickstart = HAL_GetTick(); uint32_t wait = Delay; /* Add a freq to guarantee minimum wait */ if (wait < HAL_MAX_DELAY) { wait += (uint32_t)(uwTickFreq); } while((HAL_GetTick() - tickstart) < wait) { } }

SysTick—系统定时器自带的HAL_Delay不能实现us的延时,需要修改代码,放在后面单独章节详细介绍,这里就不再赘述,大家知道HAL_Delay能够实现ms的延时。

HAL库的延时函数有一个局限性,在中断服务函数中使用HAL_Delay会引起混乱,因为它是通过中断方式实现,而Systick的中断优先级是最低的,所以在中断中运行HAL_Delay会导致延时出现严重误差。

五.实验结果

3.1.5 流水灯实验结果

将编译好的代码下载到实验板中,按下复位键,LED灯D4-D7自右向左每隔1s顺次点亮,实现流水灯效果。图3.1.5为移位显示过程的截图。

六.思考与拓展

  1. 思考GPIO为何要配置成“推挽输出”?

  2. 自己动手编程实现渐快、渐慢的流水灯效果。

七.STM32CubeMx配置GPIO

2.1 开发环境

本实验中使用stm32cubemx_v5-6-1和Keil uVision
5软件进行代码的编写和调试。STM32CubeMX 是 ST
这几年极力推荐的程序生成开发工具。所以这两年新出的 STM32 芯片,ST 直接只提供 HAL
库。在新型的 STM32 芯片中,用 HAL
库更加普遍。而Keil是一种优秀的嵌入式应用开发软件,它支持包括ARM在内的多种微控制器芯片。Keil集代码编辑、编译、仿真于一体,易学易用,界面友好,为程序调试、软件仿真等提供了众多强大、便利的功能。

2.2 编程语言

汇编语言是嵌入式系统开发初期所使用的开发语言,但它可读性、可移植性均较差,编程起来也比较困难。而C语言不仅可读性、可移植性均较强,编程难度相对较低,其目标代码以及运行速度与汇编语言相比,也无太大差别,已成为了嵌入式MCU的主流开发语言。

首先我们需要准备资料:

HAL库开发包:en.stm32cubef4_v1-25-0.zip

STM32CUBE开发工具:en.stm32cubemx_v5-6-1.zip

KEIL开发工具:MDK517.EXE

STM32F4的KEIL安装包:Keil.STM32F4xx_DFP.2.11.0.pack

JAVA安装包:chromeinstall-8u77.exe

2.3 软件架构

代码完全自主编写难度相对较大,且效率也不高。因此,为实现快速开发,各大MCU厂商均提供了完善的API外设驱动库。在编程开发过程中,通过查阅处理器的数据手册以及API函数手册,使用HAL库函数访问,可以提高代码的复用率,我们使用ST
官方的 STM32CubeMX 图形工具生成一个工程模板,从而提高工作效率。

2.4 新建工程

以下将介绍如何通过在STM32CubeMX中配置软件,并生成代码后在Keil
5中编译、下载到Cortex-M4嵌入式开发板中。

大多数情况下,我们都使用STM32CubeMX
来生成工程的时钟系统初始化代码以及外设的初始化代码,因为用户控制逻辑代码是无法在
STM32CubeMX 中完成的,需要用户自己根据需求来实现。使用 STM32CubeMX
配置工程的一般步骤为:

  1. 工程初步建立和保存

  2. RCC 设置

  3. 时钟系统(时钟树)配置

  4. GPIO 功能引脚配置

  5. 生成工程源码

  6. 编写用户代码

接下来我们将按照上面 6 个步骤,依次教大家使用 STM32CubeMX
工具生成一个完整的工程。

  1. 打开STM32CubeMX软件。如图2.4.1所示。

2.4.1

  1. 点击新建工程按钮之后,会弹出 MCU
    选择窗口。我们依次在选项卡选择我们使用的芯片STM32F429IGT
    和对应的参数,然后选择对应的芯片型号,最后点击start project
    按钮。如图2.4.2所示。


2.4.2

  1. 保存工程为LED,如图2.4.3所示。

2.4.3

  1. 配置管脚,鼠标左键点击需要配置的管脚选择相应的功能,PH0/PH1设置为晶振管脚和PF6/PF7/PI10/PI11设置为LED管脚。如图2.4.4所示。

2.4.4

  1. 时钟使能,使能HSE时钟,如图2.4.5所示。

2.4.5

  1. 配置时钟为外部25M输入,内部时钟为168M。。如图2.4.6所示。

2.4.6

  1. 在project manager
    目录下的project中选择生成代码的开发工具和版本号。如图2.4.7所示。

2.4.7

  1. 在project manager 目录下的code generator中选择生成代码的文件.
    如图2.4.8所示。

2.4.8

  1. 生成代码,点击按钮GENERATE CODE,生成代码。如图2.4.9所示。

2.4.9

  1. 生成代码,打开代码生成存放位置,文件如图。如图2.4.10所示。

2.4.10

  1. 用KEIL打开生成代码。如图2.4.11所示。

2.4.11

  1. 编译代码。点击按钮,如图2.4.12所示。

2.4.12

  1. 添加LED灯代码。管脚输出高电平,点亮四个LED灯,如图2.4.13所示。

2.4.13

  1. 生成代码分析。Main函数中一共有三个初始化函数;HAL_Init()用于初始化HAL库;SystemClock_Config()用于初始化时钟,MX_GPIO_Init()初始化使用到的GPIO口;如图2.4.14所示。

2.4.14

  1. 生成代码分析。SystemClock_Config()用于配置单片机内部时钟总线,包括选择时钟源,时钟倍频系数,AHB时钟为168M,HCLK时钟为180M,APB1时钟为42M,APB2时钟为84M;如图2.4.15所示。

2.4.15

  1. 生成代码分析。MX_GPIO_Init()用于初始化GPIO管脚配置,包括时钟,输出模式,管脚有无上拉/下拉电阻,速度等;如图2.4.16所示。

2.4.16

  1. 添加代码位置分析。代码位置添加在USER CODE BEGIN后面,USER CODE
    END前面;LED灯管脚输出高电平,LED变亮,在while(1)循环中配置LED管脚一直输出高电平;如图2.4.17所示。

2.4.17

  1. 点击“Options for Target”按钮。选择“Debug”选项板,下载器选择“CMSIS-DAP
    Debugger”。如图2.4.18所示。

2.4.18

  1. 选择“Utilities”选项板,点击“Settings”按钮。点击“Add”按钮,选择1M
    Flash,点击“确定-OK”按钮,完成设置。如图2.4.19所示。

2.4.19

  1. 点击“Build”按钮,编辑整个工程。如图2.4.20(a)所示。编译成功。如图2.4.20(b)所示。

(a)

(b)

2.4.20

  1. 用USB电缆将电脑与实验板的“DAP-LINK”接口连接,点击“LOAD”按钮,将程序下载到实验板的STM32F429。如图2.4.21所示。

2.4.21

2.5 使用debug功能进行调试

以下将介绍如何使用“Debug”功能进行代码调试。

  1. 将编译好的代码下载完毕后,点击“Debug”按钮,进入调试状态。如图2.5.1所示。

debug1

2.5.1

  1. 根据需要设置完毕断点以及所需观察的变量后,点击“Reset”按钮进行复位;点击“Run”按钮开始运行程序;若程序卡住,可点击“Stop”按钮停止运行。如图2.5.2所示。

debug2
debug3
debug4

  1. (b) (c)

2.5.2

2.6 工程添加文件

以下将介绍如何为工程添加源文件。以后自己添加程序都添加在User文件夹下。

1.在左侧“LED”栏中,单击右键选择Add
Group,新建一个组组,并改名为“User”。如图2.6.1(a)(b)所示。

(a)

(b)

2.6.1

2.新建一个文件。如图2.6.2所示。在文件中输入内容。如图2.6.2所示。

2.6.2

3.点击“Manage Project Items”按钮。选择“LED”-“User”,点击按钮“Add
Files…”,然后,选择自己添加的文件。如图2.6.3所示。

2.6.3

4.添加自己编写文件。选择“user”,点击按钮“Add
Files…”,然后,选择文件后,点击“Add”按钮加入其中;点击“OK”按钮。如图2.6.4所示。

2.6.4

5.KEIL添加编译路径。点击“Options for
Target”按钮。如图2.6.5(a)所示。选择C/C++选项版,点击“Include
Paths”“…”按钮,添加User文件下.h文件的路径。如图2.6.5(b)所示。选择路径…User添加,并点击“OK”按钮。如图2.6.5(c)所示。

(a)

(b)

(c)

2.6.5

1.1 Cortex-M4嵌入式开发板框架设计

Cortex-M4嵌入式开发板主要包括如下四部分:

  1. 核心STM32F407IGT6芯片;

  2. 外围设备:包括LED灯、按键开关、数码管、蜂鸣器、UART接口、LCD屏接口、USB接口等;板载芯片包括AT24C02、CH455H等;开发板上还集成了电源、下载器等模块;

  3. 开发板的上面JD1黄排母是供摄像头模块OV2640使用;JD2黄排母供SPI/IIC接口lcd屏幕使用;P1排母引出FSMC总线,供大尺寸LCD屏使用;J2和J3单排针共有56个通用IO口和一些GND、Power通道;背面P2排母和正面P1排母管脚相同;

  4. USB下载电缆。开发板自带DAP-LINK仿真器,实现开发板与电脑之间的传输功能,用以下载代码、串口通信和给实验板供电;

图1.1.1是Cortex-M4嵌入式开发板实物图。STM32F4芯片的介绍将在1.2节给出,外围功能模块的介绍将在1.3节给出。

1.1.1 Cortex-M4嵌入式开发板正面

1.2 ARM微处理器简介

ARM
STM32处理器

1.3 Cortex-M4嵌入式开发板外围功能模块介绍

1.3.1 输入操作类模块

该模块包括2个和ARM直接相连按键开关和16个和CH455H相连按键开关,用来输入外部操作信号,CH455H芯片和ARM通过IIC总线通信。

  1. 图1.3.1和图1.3.2为2个普通按键开关KEY1-KEY2的硬件连接图。表1.3.1为其引脚说明。按键开关一端接地,另一端接至STM32F4对应引脚,并通过上拉电阻接至3.3V电源。按键按下,与地接通,输入低电平;按键释放,通过上拉电阻接至电源,输入高电平。

1.3.1 普通按键开关硬件连接图

1.3.2 复位按键开关硬件连接图

1.3.1 普通按键开关引脚说明

设备名 引脚号 PCB上硬件
KEY1 PG6 KEY17
KEY2 PG7 KEY18
  1. 图1.3.3为16个按键的硬件连接图,16个按键通过4*4矩阵方式(SEG0-SEG3;DIG0-DIG3)连接到CH455芯片端口,CH455自动扫描键盘,通过读取相应寄存器的值,就能够判断哪个按键按下。CH455与ARM通过IIC接口通信。表1.3.2为其引脚说明。


1.3.3 16个按键硬件连接图

1.3.2 CH455和ARM引脚说明

设备名 引脚号
CH455_SCL PC14
CH455_SDA PC15
CH455_INT PC13

1.3.2 输出显示类模块

该模块包括4盏LED灯、4位数码管,用来显示部分实验结果。

  1. 图1.3.4为4盏LED灯LED1-LED4的硬件连接图。表1.3.3为其引脚说明。LED灯为高电平驱动,即STM32F4向其输出高电平,则点亮;输出低电平,则熄灭。


1.3.4 LED灯硬件连接图

1.3.3 LED灯引脚说明

设备名 引脚号
LED1 PF6
LED2 PF7
LED3 PI10
LED4 PI11
  1. 图1.3.5为4位七段共阴极数码管COM1-COM4的硬件连接图。STM32通过IIC接口配置CH455H寄存器,CH455H芯片控制数码管显示和按键扫描。每位数码管由对应的位选信号DIG1-DIG4控制是否选中,低电平有效,即输出低电平则选中该位数码管;选中的七段共阴极数码管由七个字码段SEG0-SEG6以及小数点位SEG7控制是否点亮,高电平有效,即输出高电平对应的引脚点亮。IIC接口1.3.4为其引脚说明。


1.3.5 数码管硬件连接图

1.3.4 数码管引脚说明

设备名 引脚号
CH455_SCL PC14
CH455_SDA PC15
CH455_INT PC13

1.3.3串口UART模块

板载一个串口(USART3)连接到单片机STM32F103C8T6(JTAG功能)的串口,STM32F407可以通过USART3和上位机通信。

图1.3.7为STM32F407的USART3(PB10,PB11)和STM32F103C8T6的串口硬件连接图。表1.3.6为其引脚说明。

1.3.7 UART硬件连接图

1.3.6 UART引脚说明

设备名 引脚号
USART3_RX PB11
USART3_TX PB10

1.3.4 音频或发声类模块

该模块包括1个无源蜂鸣器,用来发声以表示某些实验现象或系统报警。图1.3.8为无源蜂鸣器的硬件连接图。通过STM32F4向其对应引脚输出低频的PWM波,可以使其发声。表1.3.7为其引脚说明。


1.3.8 蜂鸣器硬件连接图

1.3.7 蜂鸣器引脚说明

设备名 引脚号
BEEP PF8

1.3.5 存储器类模块

该模块包括1个EEPROM芯片24C02,作为外部存储器用来存储数据。

(1)图1.3.9为EEPROM芯片的硬件连接图。表1.3.8为其引脚说明。EEPROM存储数据掉电不丢失,EEPROM通过STM32F4的I2C引脚与之通信,实现数据的读写。

1.3.9 EEPROM芯片硬件连接图

1.3.8 EEPROM芯片引脚说明

设备名 引脚号
I2C2_SCL PF1
I2C2_SDA PF0

1.3.6 拓展IO口接口

该模块包括I2C、SPI、TIMER、ADC、DAC、UART、摄像头、TFT屏幕,主要涉及部分外设的复用IO引脚。

  1. JD2贴片排母座用于插接各种LCD模块,图1.3.16为硬件连接图。表1.3.15为其引脚说明。


1.3.16 I2C/SPI/TIMER/ADC/DAC/UART硬件连接图

1.3.15 引脚说明

设备名 引脚号 设备名 引脚号
SPI5_SCK PH6 I2C2_SCL PF1
SPI5_ MOSI PPF9 I2C2_SDA PF0
PF10 PF10 PH2 PH2
SPI5_MISO PPH7 PH3 PH3
SPI5_NSS PH5 PH4 PH4
PF4 PF4 PF2 PF2
PC2 PC2 PF3 PF3
  1. JD1模块连接0V2640摄像头模块,可通过DCMI总线数据采集。

图1.3.17 OV2640的硬件连接图。表1.3.16为其引脚说明。


1.3.17 OV2640硬件连接图

1.3.16 引脚说明

设备名 引脚号 设备名 引脚号
DCMI_PWDN PH15
DCMI_PIXCLK PA6 DCMI_D7 PI7
DCMI_D6 PI6 DCMI_D5 PI4
DCMI_D4 PH14 DCMI_D3 PH12
DCMI_D2 PH11 DCMI_D1 PH10
DCMI_D0 PH9 DCMI_RESET PH13
I2C1_SDA PB9 DCMI_HSYNC PH8
I2C1_SCL PB8 DCMI_VSYNC PI5
  1. P1和P2连接显示屏模块,可通过FMC总线控制外部TFTLCD屏幕。

图1.3.18的硬件连接图。表1.3.17为其引脚说明。


1.3.18 TFT/FSMC硬件连接图

1.3.17 P1引脚说明

设备名 引脚号 设备名 引脚号
GND GND PB10 PB10
GND GND PB11 PB11
FMC_NE3 PG10 FMC_NE2 PG9
FMC_A16 PD11 FMC_A17 PD12
FMC_A12 PG2 FMC_A13 PG3
FMC_A14 PG4 FMC_A15 PG5
FMC_A8 PF14 FMC_A9 PF15
FMC_A10 PG0 FMC_A11 PG1
3V3 3V3 PF13 PF13
GND GND FMC_D15 PD10
FMC_D14 PD9 FMC_D13 PD8
FMC_D12 PE15 FMC_D11 PE14
FMC_D10 PE13 FMC_D9 PE12
FMC_D8 PE11 FMC_D7 PE10
FMC_D6 PE9 FMC_D5 PE8
FMC_D4 PE7 FMC_D3 PD1
FMC_D2 PD0 FMC_D1 PD15
FMC_D0 PD14 RESET RESET
FMC_NOE PD4 FMC_NWE PD5
FMC_A18 PD13 FMC_NE1 PD7
  1. J2单排针扩展IO。

1.3.19 J2硬件连接图

  1. J3单排针扩展IO。

1.3.20 J3硬件连接图

基于FPGA的数字实验指导书
(Anlogic EG4S20BG256版本)

大拇指安路FPGA开发板简介(Anlogic EG4S20BG256版本) 大拇指安路FPGA开发板——我的第一个工程 大拇指安路FPGA开发板——编译和下载 大拇指安路FPGA开发板——使用TD中的硬件逻辑分析仪ChipWatcher

使用TD中的硬件逻辑分析仪ChipWatcher

当程序下载到FPGA里后,我们可以通过ChipWatcher这个调试工具来分析程序的运行情况,同时通过触发条件设定来观察特定时刻的波形。

这里我们以流水灯程序为例来使用ChipWatcher观察移位寄存器的移位过程。

在程序里我们使用两个分频器将50MHz的输入时钟分频产生1Hz和100Hz的两个时钟,1Hz的时钟用来驱动移位寄存器,100Hz的时钟给ChipWatcher来采样移位寄存器的输出。

img

下面我们简要介绍一下如何使用ChipWatcher来观察移位寄存器的输出。

1、在Tools中选择Debug Tools,打开ChipWatcher。

img

2、选择创建一个新设计。

img

3、选择ChipWatcher的采样时钟和存储深度,注意如果被观察的信号速度较慢,需要分频一个慢速时钟来观察它,同时提高存储深度。

img确保连接JTAG识别到器件,首先选择采样时钟源和存储深度

img img

选择合适的时钟做为采样时钟,这里我们要观察的信号是1Hz变化一次的信号,因此我们将采样时钟选为100Hz;

img

存储深度选为1K,这样一共可以采集10秒钟来看到完整的移位周期。

2、设置被观察的信号。

img

img

选择LED_Out作为被观察对象

img

设置触发条件为LED_Out = 00000001

img

在File中点击Save,将ChipWatcher存为run_led.cwc文件并根据提示添加到当前工程中

img

根据提示重新编译并生成.bit文件

​ 5、下载含有ChipWatcher功能的.bit文件

img

3、点击单次捕获并观察捕获的波形,100Hz采集1000个点需要10秒钟,请耐心等待。

img

点击单次运行,ChipWatcher将等待触发条件到来把捕获到的数据显示出来。

img

此时可以看到数据捕获的结果,触发条件处有一条蓝线,因为是预触发模式,因此触发条件的前后一段数据都被保存下来。

基于FPGA的数字实验指导书
(Anlogic EG4S20BG256版本)

大拇指安路FPGA开发板简介(Anlogic EG4S20BG256版本) 大拇指安路FPGA开发板——我的第一个工程 大拇指安路FPGA开发板——编译和下载 大拇指安路FPGA开发板——使用TD中的硬件逻辑分析仪ChipWatcher

TangDynasty®(TD)软件是安路科技自主开发的FPGA集成开发环境,支持工业界标准的设计输入,包含完整的电路优化流程以及丰富的分析与调试工具,并提供良好的第三方设计验证工具接口,为所有基于安路科技FPGA产品的应用设计提供有力支持。

以下是在TD中建立流水灯工程并下载到DMZ_Anlogic开发板中验证的全过程:

1、新建工程。如图1.23所示,打开TD然后点击红圈处的按钮,即可新建一个工程。

img

1.23 新建工程

2、确定工程名。如图1.24所示,在打开的对话框中分别选择工程路径,设置工程名字。这里我的工程路径是D:/FPGA/anlogic/run_led,这里需要注意的是文件夹必须先自行建好,否则软件会报错“路径不存在”,另外TD的路径中不能出现中文名。接下来的工程名和顶层文件名取run_led,然后点击“Next”。

img

1.24 建立工程

3、添加已有的设计文件或新建设计文件。如图1.25所示,在这里我们选择添加现有的设计文件(事先将要使用的.v文件拷贝到工程文件夹下):

img

img

img

1.25 添加已有的设计文件

添加已有的设计文件后,TD软件会自动识别顶层文件并将其设置为“Top”

4、分配引脚,点击Tools下面的IO Constraint调出IO分配界面,如图1.26。

1624343884(1)

1.26 调出引脚分配界面

img

1.27 分配引脚

这里需要根据原理图的硬件连接关系分配引脚,点击保存TD会将图形化设置保存为xxx.adc文件(需自定义文件名),并自动将该adc文件(Anlogic Design Constrain)添加到工程中。

img

1.28 保存为adc文件并添加到当前工程

后续设计,也可以直接修改adc文件,并手动将adc文件添加到工程下。例如下面是刚才生成的adc文件,可以根据新的工程手动修改网络名和IO位置,另存后给其他工程使用。

set_pin_assignment { CLK } { LOCATION = R7; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }

set_pin_assignment { LED_Out[0] } { LOCATION = B14; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[1] } { LOCATION = B15; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[2] } { LOCATION = B16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[3] } { LOCATION = C15; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[4] } { LOCATION = C16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[5] } { LOCATION = E13; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[6] } { LOCATION = E16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { LED_Out[7] } { LOCATION = F16; IOSTANDARD = LVCMOS33; }

set_pin_assignment { RSTn } { LOCATION = A9; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }

这样我们第一个工程就已经建好,接下来我们需要编译工程(大拇指安路FPGA开发板——编译和下载),生成.bit文件,写入芯片进行验证。