CKS32F4xx

本课讲为大家讲解CKS32F4xx系列产品的FSMC应用实例,FSMC全称是Flexible Static Memory Controller,读作灵活的静态存储控制器,顾名思义,MCU可以通过FSMC扩展静态内存,灵活则是因为支持PC卡/CF卡、SRAM、PSRAM、NOR Flash和NAND Flash等几种模式,本应用实例主要是帮助读者们学习驱动NAND Flash。注意,FSMC并不能支持如SDRAM这类型动态存储器,需要FMC功能才支持。

FSMC简介

FSMC主要框图

FSMC的主要思路是内核想访问存储器,但是存储器需要接收到特定的数据/地址/控制等信号,内核并不好操作,于是就诞生了FSMC外设,有了这个外设我们就无须自己写复杂的硬件时序,由配置好的FSMC替我们完成。根据配置的不同,FSMC可用于控制ROM、SRAM、NOR Flash、PSRAM和NAND Flash,其结构框图如图1所示。

1.png

图1 FSMC框图

从框图可以了解到,FSMC外设包含4个模块:AHB总线接口(包含配置寄存器)、NOR Flash/PSARM控制器、NAND Flash/PC 卡控制器、外部接口。

(1)内核通过AHB总线发送总线事务请求,AHB时钟(HCLK)是FSMC的参考时钟。

(2)配置寄存器按配置形成扩展外设的通信协议,驱动相应的存储器控制器,进而控制外设。

(3)两大控制器用于生成适当的时序,如NOR Flash/PSRAM控制器用于驱动8位、16位、32位的异步SRAM和ROM、异步或突发模式的NOR Flash和PSRAM;NAND/PC卡控制器用于驱动8位或16位的NAND Flash以及16位的PC卡兼容设备。

(4)不同外设共用一组地址数据总线、读写使能和输入等待线,此共用信号基本上涵盖了扩展外设读写所需要的所有信息,地址用于寻址,读写使能和输入等待线用于控制数据传输方向,并标识数据有效时间以同步数据。FSMC每次访问外设时,都会切换片选信号,如NOR Flash的NE信号,NAND Flash和PC卡的NCE信号,其余外设接口信号根据具体外设驱动方式分配。

FSMC设备地址映射

FSMC的外部设备地址映射划分在0x60000000~0x9FFFFFFF这一段存储空间内,我们可以直接以读写内存的形式访问存储设备,类似访问片上外设寄存器一样方便,如图2所示将外部存储器划分为四个存储块(Bank),每个Bank对应256M字节的空间,加起来FSMC总共管理着1G字节的空间。Bank1用于NOR Flash/PSRAM,最多可以连接4个存储器,Bank2/3可以各接入1个NAND Flash,Bank4则用于扩展PC卡兼容设备。本文主要探讨NAND Flash驱动,因此主要讨论块2、块3的相关配置。

2.png

图2 FSMC存储区域

对于NAND Flash/PC卡控制器,块2.3.4被划分为表1所示的访问空间,地址映射分通用区和特性区,块4-PC卡相比多了一个I/O空间,分别由各自的时序寄存器控制访问时序。

3.png

表1 NAND Flash/PC卡地址

对于NAND Flash,通用空间和特性空间在其低256K字节部分划分了3个区,前64K为地址区,中64K为命令区,后128K为数据区,地址范围如表2所示。访问NAND存储空间流程简单描述为:先发送命令到命令区,其次发送需要操作的地址至地址区,最后在数据区写入或读出数据,以上流程只需要在三个区内任意地址执行即可。

4.png

表2 每个存储块的低256K字节

NAND Flash/PC卡存储器控制器

FSMC内部有两个独立的控制器分别用于驱动NOR/PSRAM内存和NAND/PC卡。由于本文所涉及的例子是驱动一个8位NAND Flash(型号K9F1G08U0E),所以这里主要讲述一下NAND/PC卡控制器。

支持设备和接口

表3列出了FSMC支持的NAND设备、访问模式和总线事务,可以看到NAND Flash/PC卡控制器能够以异步方式访问8位或16位的NAND Flash,而请求AHB操作的数据宽度可以是8位、16位、32位,为了保障数据传输的一致性,FSMC遵循以下原则:

1、AHB操作的数据宽度和外部存储器宽度相同时,无传输一致性问题。

2、AHB操作的数据宽度大于外部存储器宽度,FSMC将AHB操作分割成几个连续的较小数据宽度的存储器操作,以适应外部存储器。

3、AHB操作的数据宽度小于外部存储器宽度,异步传输时不能进行写的操作,只能进行读操作,如表3灰色部分所示。

5.png

表3 支持的存储器和事务

表4列出了用于连接8位NAND Flash的接口信号,我们主要通过地址区(A17)、命令区(A16)和数据区(D[7:0])这三个区来访问NAND Flash,注意名称前缀N表示低电平有效。

6.png

表4 8位NAND存储器接口信号

NAND Flash操作流程

以NAND Flash页读取操作流程为例,说明控制器是怎么参与操作的:

1、 根据所选8位NAND Flash的特性,配置控制寄存器FSMC_PCRx、通用区时序寄存器 FSMC_PMEMx和特性区时序寄存器FSMC_PATTx,进而配置并使能相应的存储区域

2、 根据NAND Flash命令集在通用区域的命令区执行字节写操作,将页读取的命令写入命令区,在NWE低电平时,Flash的CLE输入有效,将命令锁存。

3、 在通用区域或特性区域的地址区写入表示读操作起始地址的相应字节,在NEW低电平时,Flash的ALE输入有效,将地址锁存为读操作起始地址。

4、 借助特性存储器空间,可设置预等待功能,当写入地址结束时,控制器会等待存储器准备好,R/NB信号变为低电平后方可访问新的存储空间。

5、 在通用区域的数据区按字节读出数据完成页读取操作。

6、 后续可继续执行第五步读出数据而无需再进行命令和地址的写操作,或重新执行第三步写入新的地址,或重新执行第二步发送新的命令。

NAND Flash/PC卡寄存器

控制寄存器FSMC_PCRx

7.png

位1 PWAITE:等待特性使能位=1时,使能等待特性,注意当使能等待特性后,需配置相应等待时间,如使用读芯片忙脚作为反馈时,可关闭等待特性。

位2 PBKEN:NAND Flash/PC卡存储区域使能位=1时使能存储块功能,于FSMC_NANDcmd函数内使能。

位3 PTYP:存储器类型=1时为NAND Flash,设置0时为PC卡、PC卡等。

位5:4 PWID:数据总线宽度,用于定义外部存储器设备宽度,00时为8位,01则16位。位6 ECCEN:ECC计算逻辑使能位=1时使能ECC逻辑。

位12:9 TCLR: CLE到RE的延迟时间,从0000到1111可配置1到16个HCLK周期。

位16:13 TAR: ALE到RE的延迟时间,从0000到1111可配置1到16个HCLK周期。

位19:17 ECCPS: ECC页大小,自定义扩展ECC的页大小,000为256字节,001为512字节,最大配置101为8192字节。

通用存储器空间时序寄存器FSMC_PMEMx

8.png

位7:0 MEMSETx:通用存储器x的建立时间,定义为读写使能命令(NWE,NOE)前建立地址所需要的HCLK周期数,可设置0x00到0xFF表示2到257个周期数(NAND Flash)。

位15:8 MEMWAITx:通用存储器x的等待时间,定义为保持读写使能命令(NWE,NOE)所需要的HCLK周期数,可设置0x01到0xFF表示2到256个周期数(再加上NWAIT变低的等待时间),0x00用于保留。

位23:16 MEMHOLDx:通用存储器x的保持时间,定义为读写禁止命令(NWE,NOE)后保持地址信号或数据信号的HCLK周期数,可设置0x01到0xFF表示1到255个周期数,0x00用于保留。

位31:24 MEMHIZx:数据总线高阻态时间,当进行写操作时,数据总线需要保持一段时间的高阻态状态,可设置0x00到0xFF表示1到256个HCLK周期数(NAND Flash)。

特性存储器空间时序寄存器FSMC_PATTx与通用寄存器一样,有同样的4个参数分别为ATTHIZx、ATTHOLDx、ATTWAITx和ATTSETx,可自行对应参考。

驱动NAND Flash示例

FSMC代码示例

(1)FSMC初始化配置:A.首先开启GPIO、FSMC、AFIO复用等三个时钟,按外接引脚配置除PD6以外所有引脚复用推挽输出,PD6忙脚作为上拉输入,然后所有脚开启FSMC复用功能。B.FSMC寄存器配置,使用外接忙脚作为应答,因此关掉等待特性,数据宽度选择8bit,使能ECC校验功能,ECC页大小为512字节,各信号时序因为外接模块,最后初始化并使能复位。

(2)进行读取NAND Flash ID号的操作,按照上一节的控制逻辑,ReadID,必须发送命令90H(H代表16进制)到命令区(命令区、地址区和数据区的地址如表2所示),接着发送0x00到地址区,最后在数据区读回32位的ID号0xECF10095。

(3)写入操作前,需要先整块擦除,命令区输入0x60,然后地址区输入要擦除的地址,再第二轮输入命令区0xD0执行擦除,函数最后返回读取当前状态的值,会持续等待设备擦除完毕或超时退出。

(4)填充TxBuffer用于写入实验,目前填充共2K的空间,刚好一个页容量。

(5)页写入TxBuffer的数据,函数的三个参数为TxBuffer、写入地址和写入页的数量,给定TxBuffer数据,写入地址为WriteReadAddr.Zone = 0x00、WriteReadAddr.Block = 0x00、WriteReadAddr.Page = 0x00,写入页数量为1。当写入页数量不为0、地址状态有效且状态已准备好时,进入循环,发送写入一次命令80H,写入列地址表示页内偏移,无偏移所以为0x00,写入行地址为当前块地址,在将Txbuffer依次连续写入数据区,完成后二次命令0x10结束写入,读NAND Flash的忙脚等待写入完成信号,等待完成后行地址递增,可进行下一个页的写入。

(6)页读取操作同页写入,命令区一次写入0x00,二次写入0x30,列地址0x00,行地址选当前页地址。二次命令写入后才读出数据给到RxBuffer。

(7)最后利用串口打印,读ID、比较TxBuffer和RxBuffer(比较前100个数据)是否一致可以看到ID识别为0xECF10095,TxBuffer和RxBuffer数据一致。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 9

Inter-IC Sount Bus(I2S)是针对数字音频设备之间的音频数据传输而制定的一种总线标准。I2S标准中,既规定了硬件接口规范,也规定了数字音频数据的格式。

I2S总线接口介绍

I2S总线接口有3个主要信号,但只能实现数据半双工传输,后来为实现全双工传输有些设备增加了扩展数据引脚。CKS32F4xx系列控制器支持扩展的I2S总线接口。

SD(Serial Data)

串行数据线,用于发送或接收两个时分复用的数据通道上的数据(仅半双工模式),如果是全双工模式,该信号仅用于发送数据。

WS(Word Select)

字段选择线,也称帧时钟(LRC)线,表明当前传输数据的声道,不同标准有不同的定义。WS线的频率等于采样频率(FS)。

CK(Serial Clock)

串行时钟线,也称位时钟(BCLK),数字音频的每一位数据都对应有一个CK脉冲,它的频率为:2*采样频率*量化位数,2代表左右两个通道数据。

ext_SD(extend Serial Data)

扩展串行数据线,用于全双工传输的数据接收。另外,有时为使系统间更好地同步,还要传输一个主时钟(MCK),CKS32F4xx系列控制器固定输出为256*FS。

CKS32F4xx系列控制器有两个I2S,I2S2和I2S3,两个的资源是相互独立的,但分别与SPI2和SPI3共用大部分资源。这样I2S2和SPI2只能选择一个功能使用,I2S3和SPI3相同道理。资源共用包括引脚共用和部分寄存器共用,当然也有部分是专用的。控制器的I2S支持两种工作模式,主模式和从模式;主模式下使用自身时钟发生器生成通信时钟。I2S功能框图如下图所示:

1.png

功能引脚

I2S的SD映射到SPI的MOSI引脚,ext_SD映射到SPI的MISO引脚,WS映射到SPI的NSS引脚,CK映射到SPI的SCK引脚。MCK是I2S专用引脚,用于主模式下输出时钟或在从模式下输入时钟。I2S时钟发生器可以由控制器内部时钟源分频产生,亦可采用CKIN引脚输入时钟分频得到,一般使用内部时钟源即可。

参考下表CKS32F4xx系列控制器I2S引脚分布:

2.png

数据寄存器

I2S有一个与SPI共用的SPI数据寄存器(SPI_DR),有效长度为16bit,用于I2S数据发送和接收,它实际由三个部分组成,一个移位寄存器、一个发送缓冲区和一个接收缓冲区,当处于发送模式时,向SPI_DR写入数据先保存在发送缓冲区,总线自动把发送缓冲区内容转入到移位寄存器中进行传输;在接收模式下,实际接收到的数据先填充移位寄存器,然后自动转入接收缓冲区,软件读取SPI_DR时自动从接收缓冲区内读取。I2S是挂载在APB1总线上的。

逻辑控制

I2S的逻辑控制通过设置相关寄存器位实现,比如通过配置SPI_I2S配置寄存器(SPI_I2SCFGR)的相关位可以实现选择I2S和SPI模式切换、选择I2S工作在主模式还是从模式并且选择是发送还是接收、选择I2S标准、传输数据长度等等。SPI控制寄存器2(SPI_CR2)可用于设置相关中断和DMA请求使能,I2S有5个中断事件,分别为发送缓冲区为空、接收缓冲区非空、上溢错误、下溢错误和帧错误。SPI状态寄存器(SPI_SR)用于指示当前I2S状态。

时钟发生器

I2S比特率用来确定I2S数据线上的数据流和I2S时钟信号频率。I2S比特率=每个通道的位数×通道数×音频采样频率。图为I2S时钟发生器内部结构。I2SxCLK(x可选2或3)可以通过RCC_CFGR寄存器的I2SSRC位选择使用PLLI2S时钟作为I2S时钟源或I2S_CKIN引脚输入时钟作为I2S时钟源。一般选择内部PLLI2S(通过R分频系数)作为时钟源。例程程序设置PLLI2S时钟为258MHz,R分频系数为3,此时I2SxCLK时钟为86MHz。

3.png

SPI_I2S预分频器寄存器(SPI_I2SPR)的MCKOE位用于设置MCK引脚时钟输出使能;ODD位设置预分频器的奇数因子,实际分频值=I2SDIV*2+ODD;I2SDIV为8位线性分频器,不可设置为0或1。当使能MCK时钟输出,即MCKOE=1时,采样频率计算如下:

FS=I2SxCLK/[(16*2)*((2*I2SDIV)+ODD)*8)](通道帧宽度为16bit时)

FS=I2SxCLK/[(32*2)*((2*I2SDIV)+ODD)*4)](通道帧宽度为32bit时)当禁止MCK时钟输出,即MCKOE=0时,采样频率计算如下:

FS=I2SxCLK/[(16*2)*((2*I2SDIV)+ODD))](通道帧宽度为16bit时)

FS=I2SxCLK/[(32*2)*((2*I2SDIV)+ODD))](通道帧宽度为32bit时)

I2S初始化结构体介绍

标准库函数对I2S外设建立了一个初始化结构体I2S_InitTypeDef。初始化结构体成员用于设置I2S工作环境参数,并由I2S相应初始化配置函数I2S_Init调用,这些设定参数将会设置I2S相应的寄存器,达到配置I2S工作环境的目的。

代码清单:I2S_InitTypeDef结构体

typedef struct
{  
    uint16_t I2S_Mode;           
    uint16_t I2S_Standard;      
    uint16_t I2S_DataFormat;    
    uint16_t I2S_MCLKOutput;    
    uint32_t I2S_AudioFreq;     
    uint16_t I2S_CPOL;        
}I2S_InitTypeDef;

(1)I2S_Mode:I2S模式选择,可选主机发送、主机接收、从机发送以及从机接收模式,它设定SPI_I2SCFGR寄存器I2SCFG位的值。一般设置CKS32控制器为主机模式,当播放声音时选择发送模式;当录制声音时选择接收模式。

(2)I2S_Standard:通信标准格式选择,可选I2S Philips标准、左对齐标准、右对齐标准、PCM短帧标准或PCM长帧标准,它设定SPI_I2SCFGR寄存器I2SSTD位和PCMSYNC位的值。一般设置为I2S Philips标准即可。

(3)I2S_DataFormat:数据格式选择,设定有效数据长度和帧长度,可选标准16bit格式、扩展16bit(32bit帧长度)格式、24bit格式和32bit格式,它设定SPI_I2SCFGR寄存器DATLEN位和CHLEN位的值。对应16bit数据长度可选16bit或32bit帧长度,其他都是32bit帧长度。

(4)I2S_MCLKOutput:主时钟输出使能控制,可选使能输出或禁止输出,它设定SPI_I2SPR寄存器MCKOE位的值。为提高系统性能一般使能主时钟输出。

(5)I2S_AudioFreq:采样频率设置,标准库提供采样采样频率选择,分别为8kHz、11kHz、16kHz、22kHz、32kHz、44kHz、48kHz、96kHz、192kHz以及默认2Hz,它设定SPI_I2SPR寄存器的值。

(6)I2S_CPOL:空闲状态的CK线电平,可选高电平或低电平,它设定SPI_I2SCFGR寄存器CKPOL位的值。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com

围观 10

CKS32F4xx系列使用高性能的32位内核,支持浮点运算单元(FPU),同时还支持DSP指令以及存储保护(MPU)用来加强应用的安全性。

DSP介绍

CKS32F4xx系列拥有两个DSP指令:MAC指令(32位乘法累加)和SIMD指令。32位乘法累加(MAC)单元包括新的指令集,能够在单周期内完成一个32×32+64→64的操作或两个16×16的操作。而SIMD指令与硬件乘法器一起工作(MAC),使所有这些指令都能在单个周期内执行。受益于SIMD指令的支持,CKS32F4xx系列能在单周期内完成高达32×32+64→64的运算,为其他任务释放处理器的带宽,而不是被乘法和加法消耗运算资源。

DSP源码库具有以下功能:

BasicMathFunctions

提供浮点数的各种基本运算函数,如向量加减乘除等运算。

CommonTables

arm_common_tables.c文件提供位翻转或相关参数表。

ComplexMathFunctions

复杂数学功能,如向量处理,求模运算的。

ControllerFunctions

控制功能函数。包括正弦余弦,PID电机控制,矢量Clarke变换,矢量Clarke逆变换等。

FastMathFunctions

快速数学功能函数。提供了一种快速的近似正弦,余弦和平方根等,相比CMSIS计算库要快的数学函数。

FilteringFunctions

滤波函数功能,主要为FIR和LMS(最小均方根)等滤波函数。

MatrixFunctions

矩阵处理函数。包括矩阵加法、矩阵初始化、矩阵反、矩阵乘法、矩阵规模、矩阵减法、矩阵转置等函数。

StatisticsFunctions

统计功能函数。如求平均值、最大值、最小值、计算均方根RMS、计算方差/标准差等。

SupportFunctions

支持功能函数,如数据拷贝,Q格式和浮点格式相互转换,Q任意格式相互转换。

TransformFunctions

变换功能。包括复数FFT(CFFT)/复数FFT逆运算(CIFFT)、实数FFT(RFFT)/实数FFT逆运算(RIFFT)、和DCT(离散余弦变换)和配套的初始化函数。

DSP库运行环境搭建

接下来我们讲解如何搭建DSP库运行环境,只要运行环境搭建好了,使用DSP库里面的函数来做相关处理就非常简单了。在MDK里面搭建CKS32F4xx系列的DSP运行环境(使用.lib方式)是很简单的,分为3个步骤:

1、添加文件

首先,我们在例程工程目录下新建DSP_LIB文件夹,存放我们将要添加的相关文件,如图1所示:
1.png
然后,打开工程,新建DSP_LIB分组,并将.lib文件添加到工程里面,如图2所示:
2.png

2、添加头文件包含路径

添加好.lib文件后,我们要添加头文件包含路径,将第一步拷贝的Include文件夹和DSP_LIB文件夹加入头文件包含路径,如图3所示:
3.png

3、添加全局宏定义

最后,为了使用DSP库的所有功能,我们还需要添加几个全局宏定义:

1,__FPU_USED

2,__FPU_PRESENT

3,ARM_MATH_CM4

4,__CC_ARM

5,ARM_MATH_MATRIX_CHECK

6,ARM_MATH_ROUNDING

添加方法:点击魔法棒→C/C++选项卡,然后在Define里面进行设置,如图4所示:
4.png
这里,两个宏之间用“,”隔开。并且,上面的全局宏里面,我们没有添加__FPU_USED,因为这个宏定义在Target选项卡设置Code Generation的时候选择了:Use FPU(如果没有设置Use FPU,则必须设置!!),故MDK会自动添加这个全局宏,因此不需要我们手动添加了。这样,在Define处要输入的所有宏为:

CKS32F40_41xxx,USE_STDPERIPH_DRIVER,ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING 共6个。 

至此,CKS32F4xx系列的DSP库运行环境就搭建完成了。为了方便调试,本章例程我们将MDK的优化设置为-O0优化,以得到最好的调试效果。

DSP FFT测试

关于FFT这里就不再详细介绍。 如果我们要自己实现FFT算法,对于不懂数字信号处理的人来说,是比较难的,不过,DSP库里面就有FFT函数给我们调用,因此我们只需要知道如何使用这些函数,就可以迅速的完成FFT计算。DSP库里面提供了定点和浮点 FFT 实现方式,并且有基4的也有基2的,大家可以根据需要自由选择实现方式。注意:对于基4的FFT输入点数必须是4n,而基2的FFT 输入点数则必须是2n,并且基4的FFT算法要比基2的快。本章我们将采用DSP库里面的基4浮点FFT算法来实现FFT变换,并计算每个点的模值,所用到的函数有:

FFT变换用到的函数

arm_status arm_cfft_radix4_init_f32( arm_cfft_radix4_instance_f32 * S, 
uint16_t fftLen,uint8_t ifftFlag,uint8_t bitReverseFlag)
void arm_cfft_radix4_f32(const arm_cfft_radix4_instance_f32 * S,float32_t * pSrc)
void arm_cmplx_mag_f32(float32_t * pSrc,float32_t * pDst,uint32_t numSamples)

arm_cfft_radix4_init_f32用于初始化FFT运算相关参数,其中:fftLen用于指定 FFT长度(16/64/256/1024/4096),本章设置为1024;ifftFlag用于指定是傅里叶变换(0)还是反傅里叶变换(1),本章设置为0;bitReverseFlag用于设置是否按位取反,本章设置为1;最后,所有这些参数存储在一个 arm_cfft_radix4_instance_f32结构体指针S里面。

arm_cfft_radix4_f32就是执行基4浮点FFT运算的,pSrc传入采集到的输入信号数据(实部+虚部形式),同时FFT变换后的数据,也按顺序存放在pSrc里面,pSrc必须大于等于 2倍fftLen长度。另外,S结构体指针参数是先由arm_cfft_radix4_init_f32函数设置好,然后传入该函数的。 

arm_cmplx_mag_f32用于计算复数模值,可以对FFT变换后的结果数据,执行取模操作。pSrc为复数输入数组(大小为 2*numSamples)指针,指向FFT变换后的结果;pDst 为输出数组(大小为 numSamples)指针,存储取模后的值;numSamples就是总共有多少个数据需要取模。

通过这三个函数,我们便可以完成 FFT 计算,并取模值。

主函数

int main(void)
{
    arm_cfft_radix4_instance_f32 scfft;
    float time;
    u8 buf[50];
    u16 i;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
    delay_init(168);  //初始化延时函数
    Debug_USART_Config(); //初始化串口波特率为115200
    TIM3_Int_Init(65535,84-1); //1Mhz计数频率,最大计时65ms左右超出
    arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,0,1);//初始化scfft结构体,设定FFT相关参数  
    while(1)
    {
        for(i=0;i<fft_length;i++) 生成信号序列
        {
            fft_inputbuf[2*i]=100+10*arm_sin_f32(2*PI*i/FFT_LENGTH)+30*arm_sin_f32(2*PI*i*4/FFT_LENGTH)+50*arm_cos_f32(2*PI*i*8/FFT_LENGTH); //生成输入信号实部 
            fft_inputbuf[2*i+1]=0;//虚部全部为0
        }
        TIM_SetCounter(TIM3,0);//重设TIM3定时器的计数器值
        timeout=0;
        arm_cfft_radix4_f32(&scfft,fft_inputbuf); //FFT计算(基4)
        time=TIM_GetCounter(TIM3)+(u32)timeout*65536;
        sprintf((char*)buf,"%0.3fms\r\n",time/1000);
        printf("\r\nFFT基4运算运行时间为:\r\n%s", buf);
        arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH);
        printf("\r\n%d point FFT runtime:%0.3fms\r\n",FFT_LENGTH,time/1000);
        printf("FFT Result:\r\n");
        for(i=0;i<fft_length;i++)< span="">
        {
            printf("fft_outputbuf[%d]:%f\r\n",i,fft_outputbuf[i]);
        }
        delay_ms(2000);
        delay_ms(2000);
        delay_ms(2000);
    }
}

主函数里面通过我们前面介绍的三个函数:arm_cfft_radix4_init_f32、 arm_cfft_radix4_f32和arm_cmplx_mag_f32来执行FFT变换并取模值。每隔6秒就会重新生成一个输入信号序列,并执行一次FFT计算,将 arm_cfft_radix4_f32 所用时间统计出来,同时将取模后的模值通过串口打印出来。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 12

CKS32F4xx系列芯片内部嵌入一个FLASH,若FLASH存储了用户的应用程序后仍留有空间,剩余空间可被当作EEPROM使用,这比访问外部FLASH速度优势更为明显。在本章节中,将会向大家简要介绍内部FLASH,并通过一个例程来演示FLASH模拟EEPROM的操作。

内部FLASH简介

FLASH结合了ROM和RAM的优点,不仅具备了EEPROM的可擦写性能,还类似于失性随机存储器(NVRAM),不会因断电而丢失数据,且能以较快的速度读取数据。对于CKS32F4xx系列的FLASH接口,它负责管理CPU通过AHB I-Code和D-Code总线对FLASH进行访问,这个接口可以执行擦除和编程操作,同时实施了一些读写保护机制,以确保数据的安全性。此外,FLASH接口还通过指令预取和缓存机制来提高代码执行的速度。下图为系统架构内的FLASH接口连接图:

1.png

针对不同型号的CKS32F4xx系列,其FLASH容量在128K至1024K字节之间。本章中以开发板上搭载的CKS32F407VGT6为例,它的FLASH容量为1024K字节,下图是CKS32F40xx/41xx的闪存模块组织图:

2.png

由上图可知,CKS32F4的存储区主要是由主存储器、系统存储器、OTP区域和选项字节构成,各存储区简述如下:

①主存储器:该部分用来存放代码和数据常数,分为12个大小不同的扇区,主存储器的起始地址是0x08000000;

②系统存储器:主要用来存放CKS32F4的bootloader代码,此代码是出厂的时候就固化在芯片内部了,例如用串口下载程序时的bootloader(ISP下载),它专门用来给主存储器下载代码;

③OTP区域:即一次性可编程区域,一次性的,写完一次,永远不能擦除;

④选项字节:用于配置读保护、BOR级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位,相当于一些寄存器位。

内部FLASH读写操作

FLASH是以字节为最小单位进行操作,每个存储单元存放一个字节。以下是对内部FLASH读写操作的概述:

---内部FLASH指定地址读出数据

例如,从地址addr读取一个字节,可通过语句data = *(vu8*)addr来实现,若是读取一个半字或一个字,只将上面的vu8改vu16或vu32即可。

---内部FLASH的写入过程

(1)解锁:CKS32F4xx系列复位后,FLASH编程操作被写保护,只有向FLASH_KEYR寄存器写入特定序列0x45670123和0xCDEF89AB,方可解除写保护,进而操作其它相关寄存器;

(2)设置闪存操作位数:CKS32F4xx系列电源电压会影响数据的最大操作位数,由于本例中使用的开发板电压为3.3V,所以根据下述配置表,必须将FLASH_CR的PSIZE字段配置为10b,即32位并行位数,这也决定后续的擦除或编程操作必须以32位为基础进行。

3.png

(3)擦除扇区:在写入数据前,必须先擦除存储区域,CKS32标准库提供了扇区擦除和批量擦除的指令,批量擦除指令仅针对主存储区。

(4)写入数据:擦除完毕后才可写入数据,CKS32F4xx系列标准库提供了字节、半字、字和双字写入函数供用户调用,封装了对寄存器FLASH_CR和FLASH_SR的操作,具体步骤可查阅CKS32F4xx的嵌入式FLASH章节。

---其它注意事项:

①CPU时钟频率(HCLK)不能低于1MHz,不能在FLASH操作期间进行器件复位;

②FLASH执行写入或擦除操作期间,不能进行读操作,否则会导致总线阻塞。因此,FLASH写操作时,有必要写FLASH_DataCacheCmd(DISABLE),来禁止数据缓存,写完后再打开;

③写入地址必须是用户代码区以外的地址,不能覆盖用户代码,否则程序会出错,可以查看map文件,选择合适的存储空间;

④由于数据是以32bit写入的,占用4个地址位,所以写入地址必须是是4的倍数。

采用内部FLASH模拟EEPROM实验

CKS32F4xx系列有关FLASH的函数分布在文件cks32f4xx_flash.c以及cks32F4xx_flash.h中,本例中通过cks_flash_test函数演示内部FLASH的读写,该函数的执行过程如下:

① 调用FLASH_Unlock 解锁;

② 调用FLASH_DataCacheCmd禁止数据缓存;

③ 调用FLASH_EraseSector擦除待写入地址所在扇区,可由上述闪存模块组织图得知,擦除时是按字为单位进行操作,并等待FLASH操作结束进入下一步;

④ 本例中是调用FLASH_ProgramWord函数向指定地址写入指定数据,并等待FLASH操作结束进入下一步,CKS32F4xx系列官方库提供FLASH_ProgramHalfWord、FLASH_ProgramByte函数,用户可根据需求选用;

⑤ 调用FLASH_DataCacheCmd开启数据缓存;

⑥ 调用FLASH_Lock 上锁。

代码如下:

int main(void)
{
    cks_flash_test();
    while (1)
    {
    }
}
void cks_flash_test(void)
{
    FLASH_Unlock();
    FLASH_DataCacheCmd(DISABLE);
    if(FLASH_EraseSector(FLASH_Sector_1, VoltageRange_3) != FLASH_COMPLETE)
    {
        return;
    }
    if(FLASH_ProgramWord(0x08004000, 0x44332211) != FLASH_COMPLETE)
    {
        return;
    }
    FLASH_DataCacheCmd(ENABLE);
    FLASH_Lock();
}

4.png

图1

5.png

图2

主函数对cks_flash_test函数调用,cks_flash_test函数实现对指定地址0x08004000进行擦除,写入操作。通过断点调试,如图1当程序运行至FLASH_EraseSector后,0x08004000起始处的四个字节先被擦除成0xFF;如图2,当程序运行运行至FLASH_ProgramWord之后,执行向0x08004000写入0x44332211后,0x08004000起始处数据变成0x11、0x22、0x33、0x44。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 12

互补输出简介

互补输出只能在高级控制定时器(TIM1和TIM8)上使用,可以输出两路互补信号,包括主输出OCx和互补输出OCxN。基于比较输出一节的内容,OCx和OCxN都可以输出一定频率和占空比的PWM波形,且他们的极性是相反的,如图1所示,OCxREF是参考信号,OCx输出信号与参考信号相同,只是它的上升沿相对于参考信号的上升沿有一个延迟,OCxN输出信号与参考信号相反,只是它的上升沿相对于参考信号的下降沿有一个延迟,这个延迟时间,我们是可以通过死区寄存器配置的,本节中我们可以设置为0。

1.png

图1 带死区插入的互补输出

2.png

图2捕获比较通道的输出阶段

如图3所示,因为互补输出多用于PWM控制电机的项目中,所以紧急情况下的刹车控制是必不可少的,OSSR的含义是运行模式下的关闭状态选择,OSSI的含义是空闲模式下的关闭状态选择,MOE的含义是主输出使能,一般的应用模式为,当短路输入为有效状态,MOE又硬件异步清零,OSSI设置为0,禁止OC\OCN输出,OSI1\OSI1N设置为0,OC1\OC1N输出为0,达到紧急刹车的目的。

3.png

图3 控制位和输出状态

配置步骤

互补输出实际跟比较输出章节一样使用的是定时器的功能,所以相关的函数设置同样在库函数文件CKS32f4xx_tim.h和CKS32f4xx_tim.c文件中。

1)开启TIM1和GPIO时钟,配置PA7、PA8选择复用功能GPIO_AF_TIM1输出。

要使用TIM1,我们必须先开启TIM1的时钟,这点相信大家看了这么多代码,应该明白了。这里我们还要配置PA7、PA8为复用(GPIO_AF_TIM1)输出,才可以实现TIM1_CH1的互补PWM经过PA7、PA8输出。库函数使能TIM1时钟的方法是:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); //>>TIM1 时钟使能

这在前面章节已经提到过。当然,这里我们还要使能GPIOA的时钟。然后我们要配置PA7引脚映射至GPIO_AF_TIM1,复用为定时器1,调用的函数为:

GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM1); //>>GPIOA7 复用为定时器1

配置PA8引脚映射至GPIO_AF_TIM1,复用为定时器1,调用的函数为:

GPIO_PinAFConfig(GPIOA,GPIO_PinSource8,GPIO_AF_TIM1); //>>GPIOA8 复用为定时器1

这个方法跟我们串口实验讲解一样,调用的同一个函数,最后设置PA7为复用功能输出这里我们只列出GPIO初始化为复用功能的一行代码:

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //>>复用功能

这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看CKS32F4对应的数据手册,比如我们PWM实验,我们使用的是定时器1的通道1,对应的引脚PA7可以从数据手册表中查看:

3.png

2)初始化TIM1,设置TIM1的ARR和PSC等参数。

在开启了TIM1的时钟之后,我们要设置ARR和PSC两个寄存器的值来控制输出PWM的周期。这在库函数是通过TIM_TimeBaseInit函数实现的,在上一节定时器中断章节我们已经有讲解,这里就不详细讲解,调用的格式为:

TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值

TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据指定的参数初始化 TIMx

3)设置TIM1_CH1的PWM模式,使能TIM1的CH1输出。

设置TIM1_CH1为PWM模式(默认是冻结的)通过配置TIM1_CCMR1的相关位来控制TIM1_CH1的模式。在库函数中,PWM通道设置是通过函数TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里我们使用的是通道1,所以使用的函数是TIM_OC1Init()。

Void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

这种初始化格式大家学到这里应该也熟悉了,所以我们直接来看看结构体TIM_OCInitTypeDef的定义:

typedef struct
{
    uint16_t TIM_OCMode;
    uint16_t TIM_OutputState;
    uint16_t TIM_OutputNState; */
    uint16_t TIM_Pulse;
    uint16_t TIM_OCPolarity;
    uint16_t TIM_OCNPolarity;
    uint16_t TIM_OCIdleState;
    uint16_t TIM_OCNIdleState;
} 
TIM_OCInitTypeDef;

这里我们讲解一下与我们要求相关的几个成员变量:

参数TIM_OCMode设置模式是PWM还是输出比较,这里我们是PWM模式。

参数TIM_OutputState\OutputNState用来设置比较输出使能,也就是使能PWM输出到端口。参数TIM_OCPolarity\OCNPolarity用来设置极性是高还是低。参数TIM_OCIdleState和TIM_OCNIdleState用来设置空闲时的输出状态。

要实现我们上面提到的场景,方法是:

TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //>>选择模式 PWM
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //>>比较输出使能
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputState_Enable; //>>比较输出使能
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; //>>输出极性高
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //>>输出极性低
TIM_OCInitStructure. TIM_OCIdleState = TIM_OCNIdleState_Reset; //>>当MOE=0重置输出空闲状态
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset; //>>当MOE=0重置输出空闲状态
TIM_OCInitStruct.TIM_Pulse=100;//待装入捕获比较寄存器的脉冲值
TIM_OC1Init(TIM1, &TIM_OCInitStructure); //>>根据指定的参数初始化外设
TIM1 OC1 TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);  //使能TIM1在CCR上的预装载寄存器

4)使能 TIM1。

在完成以上设置了之后,我们不开启刹车功能,并使能TIM1,使能TIM1的方法前面已经讲解过:

TIM_BDTRStructure.TIM_AutomaticOutput=TIM_AutomaticOutput_Enable;//自动输出功能使能
TIM_BDTRStructure.TIM_Break=TIM_Break_Disable;//失能刹车输入
TIM_BDTRStructure.TIM_BreakPolarity=TIM_BreakPolarity_High; //刹车输入管脚极性高
TIM_BDTRStructure.TIM_DeadTime=0; //输出打开和关闭状态之间的延时
TIM_BDTRStructure.TIM_LOCKLevel=TIM_LOCKLevel_OFF;// 锁电平参数: 不锁任何位
TIM_BDTRStructure.TIM_OSSIState=TIM_OSSIState_Disable; //设置在运行模式下非工作状态选项
TIM_BDTRStructure.TIM_OSSRState=TIM_OSSRState_Disable; //设置在运行模式下非工作状态选项
TIM_BDTRConfig(TIM1,&TIM_BDTRStructure);
TIM_ARRPreloadConfig(TIM1,ENABLE); //使能TIM1在ARR上的预装载寄存器
TIM_CtrlPWMOutputs(TIM1,ENABLE);   //PWM使能主输出MOE=1
TIM_Cmd(TIM1,ENABLE);   //打开TIM1

5)修改TIM1_CCR1来控制占空比。

最后,在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改TIM1_CCR1则可以控制CH1的输出占空比。在库函数中,修改TIM1_CCR1占空比的函数是:

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare2);

理所当然,对于其他通道,分别有一个函数名字,函数格式为:

TIM_SetComparex(x=1,2,3,4)

通过以上5个步骤,我们就可以控制TIM1的CH1输出互补PWM波了。

3代码示例

添加PWM配置文件pwm.c和pwm.h。

pwm.c源文件代码如下:

//>>TIM1 PWM 部分初始化
//>>PWM 输出初始化
//>>arr:自动重装值 psc:时钟预分频数
void TIM1_PWM_Init(u32 arr,u32 psc)
{
    PIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);//TIM1 时钟使能
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能 PORTA 时钟
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM1); //PA7 复用为 TIM1
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource8,GPIO_AF_TIM1); //PA8 复用为 TIM1
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8; //GPIOF9
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度 100MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 PA7\PA8
    TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
    TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure);//初始化定时器 1
    //初始化  TIM1 Channel1 互补PWM 模式
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //>>选择模式 PWM
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //>>比较输出使能
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputState_Enable; //>>比较输出使能
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; //>>输出极性低
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //>>输出极性低
    TIM_OCInitStructure. TIM_OCIdleState = TIM_OCNIdleState_Reset; //>>当MOE=0重置输出空闲状态
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset; //>>当MOE=0重置输出空闲状态
    TIM_OC1Init(TIM1, &TIM_OCInitStructure); //>>根据指定的参数初始化外设TIM1 OC1 
    TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);  //使能TIM1在CCR上的预装载寄存器
    TIM_BDTRStructure.TIM_AutomaticOutput=TIM_AutomaticOutput_Enable;//自动输出功能使能  
    TIM_BDTRStructure.TIM_Break=TIM_Break_Disable;//失能刹车输入
    TIM_BDTRStructure.TIM_BreakPolarity=TIM_BreakPolarity_High; //刹车输入管脚极性高
    TIM_BDTRStructure.TIM_DeadTime=0; //输出打开和关闭状态之间的延时
    TIM_BDTRStructure.TIM_LOCKLevel=TIM_LOCKLevel_OFF;// 锁电平参数: 不锁任何位
    TIM_BDTRStructure.TIM_OSSIState=TIM_OSSIState_Disable; //设置在运行模式下非工作状态选项
    TIM_BDTRStructure.TIM_OSSRState=TIM_OSSRState_Disable; //设置在运行模式下非工作状态选项
    TIM_BDTRConfig(TIM1,&TIM_BDTRStructure);
    TIM_ARRPreloadConfig(TIM1,ENABLE); //使能TIM1在ARR上的预装载寄存器
    TIM_CtrlPWMOutputs(TIM1,ENABLE);   //PWM使能主输出MOE=1
    TIM_Cmd(TIM1,ENABLE);              //打开TIM1
    TIM_SetCompare1(TIM1,300); //>>设置pwm的占空比为300/500 = 60%
}

此部分代码包含了上面介绍的PWM输出设置的前5个步骤。这里我们关于TIM1的设置就不再说了。接下来,我们看看主程序里面的main函数如下:

int main(void)
{ 
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//>>设置系统中断优先级分组 2
    delay_init(168); //>>初始化延时函数
    TIM1_PWM_Init(500-1,84-1); //>>定时器时钟为 84M,分频系数为 84,所以计数频率
    //>> 84M/84=1Mhz,重装载值500,所以PWM频率为1M/500=2Khz.
    while(1) {}
}

这里,我们先设置好了NVIC终端优先级,然后初始化延时函数和timer,在timer的初始化参数中我们把PWM的频率设置成2K,将占空比设置成60%,完成PWM输出设置。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 8

CKS32F4xx系列芯片自带以太网模块,该模块包括带专用DMA控制器的MAC 802.3(介质访问控制)控制器,支持介质独立接口(MII)和简化介质独立接口(RMII),并自带了一个用于外部PHY通信的SMI接口,通过一组配置寄存器,用户可以为MAC控制器和DMA控制器选择所需模式和功能。自带以太网模块特点包括:

·支持外部PHY接口,实现10M/100Mbit/s的数据传输速率;

·通过符合IEEE802.3的MII/RMII接口与外部以太网PHY进行通信;

·支持全双工和半双工操作;

·可编程帧长度,支持高达16KB巨型帧;

·可编程帧间隔(40~96位时间,以8为步长);

·支持多种灵活的地址过滤模式;

·通过SMI(MDIO)接口配置和管理PHY设备;

·支持以太网时间戳(参见IEEE1588-2008),提供64位时间戳;

·提供接收和发送两组FIFO;

·支持DMA。

以太网主要功能及框图

1.png

图1 ETH框图

从上图可以看出,CKS32F4xx系列必须外接PHY芯片,才可以完成以太网通信的,外部PHY芯片可以通过MII/RMII接口与CKS32F4xx系列内部MAC连接,并且支持SMI(MDIO&MDC)接口配置外部以太网PHY芯片。SMI接口,即站管理接口,该接口允许应用程序通过2条线:时钟(MDC)和数据线(MDIO)访问任意PHY寄存器。该接口支持访问多达32个PHY,应用程序可以从32个PHY中选择一个PHY,然后从任意PHY包含的32个寄存器中选择一个寄存器,发送控制数据或接收状态信息。任意给定时间内只能对一个PHY中的一个寄存器进行寻址。MII接口,即介质独立接口,用于MAC层与PHY层进行数据传输。CKS32F4xx系列通过MII与PHY层芯片的连接如图2所示:

2.jpg

图2 介质独立接口信号

MII_TX_CLK:连续时钟信号。该信号提供进行TX数据传输时的参考时序。标称频率为:速率为10Mbit/s时为2.5MHz;速率为100Mbit/s时为25MHz。

·MII_RX_CLK:连续时钟信号。该信号提供进行RX数据传输时的参考时序。标称频率为:速率为10Mbit/s时为2.5MHz;速率为100Mbit/s时为25MHz。

·MII_TX_EN:发送使能信号。

·MII_TXD[3:0]:数据发送信号。该信号是4个一组的数据信号,

·MII_CRS:载波侦听信号。·MII_COL:冲突检测信号。

·MII_RXD[3:0]:数据接收信号。该信号是4个一组的数据信号。

·MII_RX_DV:接收数据有效信号。

·MII_RX_ER:接收错误信号。该信号必须保持一个或多个周期(MII_RX_CLK),从而向MAC子层指示在帧的某处检测到错误。

RMII接口,即精简介质独立接口,该接口降低了在10/100Mbit/s下微控制器以太网外设与外部PHY间的引脚数。根据IEEE 802.3u标准,MII包括16个数据和控制信号的引脚。RMII规范将引脚数减少为7个。

RMII接口是MAC和PHY之间的实例化对象。这有助于将MAC的MII转换为RMII。RMII具有以下特性:·支持10Mbit/s和100Mbit/s的运行速率;

·参考时钟必须是50MHz;

·相同的参考时钟必须从外部提供给MAC和外部以太网PHY;

·它提供了独立的2位宽(双位)的发送和接收数据路径;

CKS32F4xx系列通过RMII接口与PHY层芯片的连接如图3所示:

3.jpg

图3 精简介质独立接口信号

从上图可以看出RMII相比MII,引脚数量精简了不少。注意,图中的REF_CLK信号,是RMII和外部PHY共用的50Mhz参考时钟,必须由外部提供,比如有源晶振,或者CKS32F4xx系列的MCO输出。不过有些PHY芯片可以自己产生50Mhz参考时钟,同时提供给CKS32F4xx系列,这样也是可以的。

LWIP简介

LWIP是瑞典计算机科学院(SICS)的Adam Dunkels等开发的一个小型开源的TCP/IP协议栈,是TCP/IP的一种实现方式。LWIP是轻量级IP协议,有无操作系统的支持都可以运行,LWIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LWIP协议栈适合在低端的嵌入式系统中使用。该部分功能移植由于内容较多,请自行参考相关移植材料,本文不做叙述。

LAN8720A通讯

本章我们采用RMII接口和外部PHY芯片连接,实现网络通信功能。LAN8720A是低功耗的10/100M以太网PHY层芯片,I/O引脚电压符合IEEE802.3-2005标准,支持通过RMII接口与以太网MAC层通信,内置10-BASE-T/100BASE-TX全双工传输模块,支持10Mbps和100Mbps,可以通过自协商的方式与目的主机最佳的连接方式(速度和双工模式),支持HP Auto-MDIX自动翻转功能,无需更换网线即可将连接更改为直连或交叉连接。

具体驱动方式请参考相关demo例程。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 13

很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有U盘,FLASH芯片,SD卡等。他们各有优点,综合比较,最适合单片机系统的莫过于SD卡了,它不仅容量可以做到很大(32GB以上),支持SPI/SDIO驱动,而且有多种体积的尺寸可供选择(标准的SD卡尺寸,以及TF卡尺寸等),能满足不同应用的要求。

CKS32F4xx系列的SDIO控制器支持多媒体卡(MMC卡)、SD存储卡、SD I/O卡和CE-ATA设备等。SDIO的主要功能如下:

1. 与多媒体卡系统规格书版本4.2全兼容。支持三种不同的数据总线模式:1位(默认)、4位和8位。

2. 与较早的多媒体卡系统规格版本全兼容(向前兼容)。

3. 与SD存储卡规格版本2.0全兼容。

4. 与SD I/O卡规格版本2.0全兼容:支持良种不同的数据总线模式:1位(默认)和4位。

5. 完全支持CE-ATA功能(与CE-ATA数字协议版本1.1全兼容)。8位总线模式下数据传输速率可达48MHz(分频器旁路时)。

数据和命令输出使能信号,用于控制外部双向驱动器。

SDIO主要功能及框图

CKS32F4xx系列的SDIO控制器包含2个部分:SDIO适配器模块和APB2总线接口,其功能框图如下:

1.jpg

图1 CKS32F4xx系列SDIO控制器功能框图

复位后默认情况下SDIO_D0用于数据传输。初始化后主机可以改变数据总线的宽度(通过ACMD6命令设置)。

如果一个多媒体卡接到了总线上,则SDIO_D0、SDIO_D[3:0]或SDIO_D[7:0]可以用于数据传输。MMC版本V3.31和之前版本的协议只支持1位数据线,所以只能用SDIO_D0(为了通用性考虑,在程序里面我们只要检测到是MMC卡就设置为1位总线数据)。

如果一个SD或SD I/O卡接到了总线上,可以通过主机配置数据传输使用SDIO_D0或SDIO_D[3:0]。所有的数据线都工作在推挽模式。

SDIO_CMD有两种操作模式:

①用于初始化时的开路模式(仅用于MMC版本V3.31或之前版本)

②用于命令传输的推挽模式(SD/SD I/O卡和MMC V4.2在初始化时也使用推挽驱动)

SDIO时钟

SDIO总共有3个时钟:

1. 卡时钟(SDIO_CK):每个时钟周期在命令和数据线上传输1位命令或数据。对于多媒体卡V3.31协议,时钟频率可以在0MHz至20MHz间变化;对于多媒体卡V4.0/4.2协议,时钟频率可以在0MHz至48MHz间变化;对于SD或SD I/O卡,时钟频率可以在0MHz至25MHz间变化。

2. SDIO适配器时钟(SDIOCLK):该时钟用于驱动SDIO适配器,来自PLL48CK,一般为48Mhz,并用于产生SDIO_CK时钟。

3. APB2总线接口时钟(PCLK2):该时钟用于驱动SDIO的APB2总线接口,其频率为HCLK/2,一般为84Mhz。

我们的SD卡时钟(SDIO_CK),根据卡的不同,可能有好几个区间,这就涉及到时钟频率的设置,SDIO_CK与SDIOCLK的关系(时钟分频器不旁路时)为:

SDIO_CK=SDIOCLK/(2+CLKDIV)

其中,SDIOCLK为PLL48CK,一般是48Mhz,而CLKDIV则是分配系数,可以通过SDIO的SDIO_CLKCR寄存器进行设置(确保SDIO_CK不超过卡的最大操作频率)。注意,以上公式,是时钟分频器不旁路时的计算公式,当时钟分频器旁路时,SDIO_CK直接等于 SDIOCLK。

这里要提醒大家,在SD卡刚刚初始化的时候,其时钟频率(SDIO_CK)是不能超过400Khz的,否则可能无法完成初始化。在初始化以后,就可以设置时钟频率到最大了(但不可超过SD卡的最大操作时钟频率)。

SDIO命令与响应

SDIO的命令分为应用相关命令(ACMD)和通用命令(CMD)两部分,应用相关命令(ACMD)的发送,必须先发送通用命令(CMD55),然后才能发送应用相关命令(ACMD)。SDIO的所有命令和响应都是通过SDIO_CMD引脚传输的,任何命令的长度都是固定为48位,SDIO的命令格式如表1所示:

2.jpg

表1 SDIO命令格式

所有的命令都是由MCU发出,其中开始位、传输位、CRC7和结束位由SDIO硬件控制,我们需要设置的就只有命令索引和参数部分。其中命令索引(如CMD0,CMD1)在SDIO_CMD寄存器里面设置,命令参数则由寄存器SDIO_ARG设置。

一般情况下,选中的SD卡在接收到命令之后,都会回复一个应答(CMD0是无应答),这个应答我们称之为响应,响应也是在CMD线上串行传输的。CKS32F4xx系列的SDIO控制器支持2种响应类型,即:短响应(48位)和长响应(136位),这两种响应类型都带CRC错误检测(不带CRC的响应应忽略CRC错误标志,如CMD1响应)。

3.jpg

表2 短响应格式

4.jpg

表3 长响应的格式

同样,硬件为我们滤除了开始位、传输位、CRC7以及结束位等信息,对于短响应,命令索引存放在SDIO_RESPCMD寄存器,参数则存放在SDIO_RESP1寄存器里面。对于长响应,则仅留CID/CSD位域,存放在SDIO_RESP1~SDIO_RESP4等4个寄存器。SD存储卡总共有5类响应(R1、R2、R3、R6、R7),我们这里以R1为例简单介绍一下。R1(普通响应命令)响应输入短响应,其长度为48位,R1响应的格式如表4所示:

5.jpg

表4 R1响应格式

在收到R1响应后,我们可以从SDIO_RESPCMD寄存器和SDIO_RESP1寄存器分别读出命令索引和卡状态信息。关于其他响应的介绍,请大家参考SD卡2.0协议。

最后,我们看看数据在SDIO控制器与SD卡之间的传输。对于SDI/SDIO存储器,数据是以数据块的形式传输的,而对于MMC卡,数据是以数据块或者数据流的形式传输。本节我们只考虑数据块形式的数据传输。

6.jpg

图2 SDIO(多个)块读取操作

从上图,我们可以看出,从机在收到主机相关命令后,开始发送数据块给主机,所有数据块都带有CRC校验值(CRC由SDIO硬件自动处理),单个数据块读的时候,在收到1个数据块以后即可以停止了,不需要发送停止命令(CMD12)。但是多块数据读的时候,SD卡将一直发送数据给主机,直到接到主机发送的STOP命令(CMD12)。

7.jpg

图3 SDIO(多个)块写入操作

数据块写操作同数据块读操作基本类似,只是数据块写的时候,多了一个繁忙判断,新的数据块必须在SD卡非繁忙的时候发送。这里的繁忙信号由SD卡拉低SDIO_D0,以表示繁忙,SDIO硬件自动控制,不需要我们软件处理。

SD卡初始化流程

要实现SDIO驱动SD卡,最重要的步骤就是SD卡的初始化,只要SD卡初始化完成,那么剩下的读写操作就简单了,所以我们这里重点介绍SD卡的初始化。从SD卡2.0协议中,我们得到SD卡初始化流程图如图4所示:

8.jpg

图4 SD卡初始化流程

从图中,我们看到,不管什么卡(这里我们将卡分为4类:SD2.0高容量卡(SDHC,最大32G),SD2.0标准容量卡(SDSC,最大2G),SD1.x卡和MMC卡),首先我们要执行的是卡上电(需要设置SDIO_POWER[1:0]=11),上电后发送CMD0,对卡进行软复位,之后发送CMD8命令,用于区分SD卡2.0,只有2.0及以后的卡才支持CMD8命令,MMC卡和V1.x的卡,是不支持该命令的。

在发送CMD8后,发送ACMD41(注意发送ACMD41之前要先发送CMD55),来进一步确认卡的操作电压范围,并通过HCS位来告诉SD卡,主机是不是支持高容量卡(SDHC)。

对于支持CMD8指令的卡,主机通过ACMD41的参数设置HCS位为1,来告诉SD卡主机支SDHC卡,如果设置为0,则表示主机不支持SDHC卡,SDHC卡如果接收到HCS为0,则永远不会反回卡就绪状态。对于不支持CMD8的卡,HCS位设置为0即可。

SD卡在接收到ACMD41后,返回OCR寄存器内容,如果是2.0的卡,主机可以通过判断OCR的CCS位来判断是SDHC还是SDSC;如果是1.x的卡,则忽略该位。OCR寄存器的最后一个位用于告诉主机SD卡是否上电完成,如果上电完成,该位将会被置 1。

对于MMC卡,则不支持ACMD41,不响应CMD55,对MMC卡,我们只需要在发送CMD0后,再发送CMD1(作用同ACMD41),检查MMC卡的OCR寄存器,实现MMC卡的初始化。至此,我们便实现了对SD卡的类型区分,最后发送CMD2和CMD3命令,用于获得卡CID寄存器数据和卡相对地址(RCA)。

SD卡在收到CMD2后,将返回R2长响应(136位),其中包含128位有效数据(CID寄存器内容),存放在SDIO_RESP1~4等4个寄存器里面。通过读取这四个寄存器,就可以获得SD卡的CID信息。

CMD3用于设置卡相对地址(RCA,必须为非0),对于SD卡(非MMC卡),在收到CMD3后,将返回一个新的RCA给主机,方便主机寻址。RCA的存在允许一个SDIO接口挂多个SD卡,通过RCA来区分主机要操作的是哪个卡。而对于MMC卡,则不是由SD卡自动返回RCA,而是主机主动设置MMC卡的RCA,即通过CMD3带参数(高16位用于RCA设置),实现RCA设置。同样MMC卡也支持一个SDIO接口挂多个MMC卡,不同于SD卡的是所有的RCA都是由主机主动设置的,而SD卡的RCA则是SD卡发给主机的。

在获得卡RCA之后,我们便可以发送CMD9(带RCA参数),获得SD卡的CSD寄存器内容,从CSD寄存器,我们可以得到SD卡的容量和扇区大小等十分重要的信息。CSD寄存器我们在这里就不详细介绍了,关于CSD寄存器的详细介绍,请大家参考SD卡2.0协议。

至此,我们的SD卡初始化基本就结束了,最后通过CMD7命令,选中我们要操作的SD卡,即可开始对SD卡的读写操作了。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 6

随机数发生器简介

CKS32F4xx系列自带了硬件随机数发生器(RNG),RNG处理器是一个以连续模拟噪声为基础的随机数发生器,在主机读数时提供一个32位的随机数。

CKS32F4xx系列的随机数发生器框图如图1所示:

1.jpg

图1.RNG框图

CKS32F4xx系列的随机数发生器(RNG)采用模拟电路实现。此电路产生馈入线性反馈移位寄存器(RNG_LFSR)的种子,用于生成32位随机数。

该模拟电路由几个环形振荡器组成,振荡器的输出进行异或运算以产生种子。RNG_LFSR由专用时钟(PLL48CLK)按恒定频率提供时钟信息,因此随机数质量与HCLK频率无关。当将大量种子引入RNG_LFSR后,RNG_LFSR的内容会传入数据寄存器(RNG_DR)。

同时,系统会监视模拟种子和专用时钟PLL48CLK,当种子上出现异常序列,或PLL48CLK时钟频率过低时,可以由RNG_SR寄存器的对应位读取到,如果设置了中断,则在检测到错误时,还可以产生中断。

RNG寄存器

我们介绍下CKS32F4xx系列随机数发生器(RNG)的几个寄存器。

2.png

图2.RNG控制寄存器

图2为RNG的CR寄存器,其只有bit2和bit3有效,用于使能随机数发生器和中断。我们一般不用中断,所以只需要设置bit2为1,使能随机数发生器即可。

3.jpg

图3.RNG状态寄存器

图3为RNG状态寄存器:RNG_SR。该寄存器我们仅关心最低位(DRDY位),该位用于表示RNG_DR寄存器包含的随机数数据是否有效,如果该位为1,则说明RNG_DR的数据是有效的,可以读取出来了。读RNG_DR后,该位自动清零。

4.jpg

图4.RNG数据寄存器

图4为RNG数据寄存器:RNG_DR。在RNG_SR的DRDY位置位后,我们就可以读取该寄存器获得32位随机数值。此寄存器在最多40个PLL48CK时钟周期后,又可以提供新的随机数值。

至此,随机数发生器的寄存器,我们就介绍完了。接下来,我们看看要使用库函数操作随机数发生器,应该如何设置。

配置随机数发生器

1.使能随机数发生器时钟

要使用随机数发生器,必须先使能其时钟。随机数发生器时钟来自PLL48CK,通过AHB2ENR寄存器使能。所以我们调用使能AHB2总线外设时钟的函数使能RNG时钟即可:

RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_RNG, ENABLE);

2.使能随机数发生器

这个就是通过RNG_CR寄存器的最低位设置为1,使能随机数发生器。当然,如果需要用到中断,你快还可以使能RNG中断。本章我们不用中断。库函数中使能随机数发生器的方法为:

RNG_Cmd(ENABLE);

3.判断DRDY位,读取随机数值

经过前面两个步骤,我们就可以读取随机数值了,不过每次读取之前,必须先判断 RNG_SR寄存器的DRDY位,如果该位为1,则可以读取RNG_DR得到随机数值,如果不为1,则需要等待。

获取随机数发生器状态的函数为:

FlagStatus RNG_GetFlagStatus(uint8_t RNG_FLAG);

判断数据是否有效的入口参数为 RNG_FLAG_DRDY,所以等待就绪的方法为:

while(RNG_GetFlagStatus(RNG_FLAG_DRDY)==RESET);

判断数据有效后,然后我们读取随机数发生器产生的随机数即可,调用函数为:

uint32_t RNG_GetRandomNumber(void);

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 10

12位ADC是逐次趋近型模数转换器,它具有多达19个复用通道,可测量来自16个外部源、2个内部源(温度传感器、VREF)和VBAT通道的信号。这些通道的A/D转换可在单次、连续、扫描或不连续采样模式下进行,并将ADC的结果存储在一个左对齐或右对齐的16位数据寄存器中。

除此之外,ADC还具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限/下限。

ADC主要特性

·可配置12位、10位、8位或6位分辨率

·转换结束、注入转换结束以及发生模拟看门狗或溢出事件时产生中断

    ·单次和连续转换模式

·用于自动将通道0转换为通道“n”的扫描模式

·数据对齐以保持内置数据一致性

·可独立设置各通道采样时间

·外部触发器选项,可为规则转换和注入转换配置极性

·不连续采样模式

·双重/三重模式(具有2个或更多ADC的器件提供)

·双重/三重ADC模式下可配置的DMA数据存储

·双重/三重交替模式下可配置的转换间延迟

·ADC转换类型(参见数据手册)

·ADC电源要求:全速运行时为2.4V到3.6V,慢速运行时为1.8V

·ADC输入范围:VREF≤VIN≤VREF+

·规则通道转换期间可产生DMA请求

1.png

图1.ADC框图

注意:VREF-如果可用(取决于封装),则必须将其连接到VSSA。

ADC功能说明

1.ADC开关控制    

ADC的开关控制可通过将ADC_CR2寄存器中的ADON位置1来为ADC供电。首次将ADON位置1时,会将ADC从掉电模式中唤醒,在SWSTART或JSWSTART位置1时,启动AD转换,然后可通过将ADON位清零,用于停止转换并使ADC进入掉电模式。在此模式下,ADC几乎不耗电(只有几μA)。

2.ADC时钟    

ADC具有两个时钟方案:    

①用于模拟电路的时钟:ADCCLK,所有ADC共用此时钟,时钟来源于经可编程预分频器分频的APB2时钟,该预分频器允许ADC在fPCLK2/2、/4、/6或/8下工作,有关ADCCLK的最大值,请参见数据手册。

②用于数字接口的时钟(用于寄存器读/写访问)
此时钟等效于APB2时钟,即可通过RCC APB2外设时钟使能寄存器(RCC_APB2ENR)分别为每个ADC使能/禁止数字接口时钟。

3.通道选择

对于CKS32F4XX器件,温度传感器内部连接到通道ADC1_IN16,内部参考电压VREFINT连接到ADC1_IN17。

注意:温度传感器、VREFINT和VBAT通道只在主ADC1外设上可用

4.单次转换模式

在单次转换模式下,ADC执行一次转换,且CONT位为0时,可通过以下方式启动此模式:

·将ADC_CR2寄存器中的SWSTART位置1(仅适用于规则通道);

·将JSWSTART位置1(适用于注入通道);

·外部触发(适用于规则通道或注入通道);

完成所选通道的转换之后:

·如果转换了规则通道:

-转换数据存储在16位ADC_DR寄存器中;

-EOC(转换结束)标志置1;

-EOCIE位置1时将产生中断;

·如果转换了注入通道:

-转换数据存储在16位ADC_JDR1寄存器中;

-JEOC(注入转换结束)标志置1;

-JEOCIE位置1时将产生中断;

完成以上步骤之后,停止ADC。

5.时序图

ADC在开始精确转换之前需要一段稳定时间tSTAB,并且在ADC开始转换经过15个时钟周期后,EOC标志将置1,最后将转换结果存放在16位ADC数据寄存器中,时序如图2所示。

2.png

图2.时序图

数据对齐

    DC_CR2寄存器中的ALIGN位用于选择转换后存储的数据的对齐方式,在ADC配置时可根据需求选择左对齐和右对齐两种方式,如图3和图4所示。

3.png

图3.12位数据的右对齐

4.png

图4.12位数据的左对齐

需注意的是,注入通道组的转换数据将减去 ADC_JOFRx寄存器中写入的用户自定义偏移量,因此结果可以是一个负值,图中的SEXT位表示扩展符号值。

对于规则组中的通道,不会减去任何偏移量,因此只有十二个位有效。

可独立设置各通道采样时间

ADC会在数个ADCCLK周期内对输入电压进行采样,在进行配置时,可通过修改ADC_SMPR1和ADC_SMPR2寄存器中的SMP[2:0]位来更改相应的周期数,对于ADC的多个通道而言,每个通道均可以使用不同的采样时间进行采样。
总转换时间的计算公式如下:

Tconv=采样时间+12个周期

示例:

ADCCLK=30MHz且采样时间=3个周期时:
Tconv=3+12=15个周期=0.5μs(APB2为60MHz时)

温度传感器

CKS32有一个内部的温度传感器,温度传感器可用于测量器件的环境温度(TA)。该温度传感器内部连接到ADC1_IN16通道,ADC1用于将传感器输出电压转换为数字值,图5显示了温度传感器框图,当不使用时,可将传感器置于掉电模式。
注意:必须将TSVREFE位置1才能同时对两个通道进行转换。ADC1_IN16(温度传感器)和ADC1_IN17(VREFINT)。
主要特性:

·支持的温度范围:-40°C到125°C

·精度:±1.5°C

5.png

图5.温度传感器与Vrefint通道框图

VSENSE是连接到ADC1_IN16的输入:

CKS32内部温度传感器的使用较为简单,只要设置一下内部ADC,并激活其内部通道即可。关于ADC的设置,在之前的章节已经进行了详细的介绍,本章节仅做简要讲解,接下来介绍一下如何读取内部温度传感器采集到的温度。

读取温度,要使用传感器,请执行以下操作:

1) 选择ADC1_IN16输入通道;

2) 选择一个采样时间,该采样时间要大于数据手册中所指定的最低采样时间;

3) 在ADC_CCR寄存器中将TSVREFE位置1,以便将温度传感器从掉电模式中唤醒;

4) 通过将SWSTART位置1(或通过外部触发)开始ADC转换;

5) 读取ADC数据寄存器中生成的VSENSE数据;

6) 使用以下公式计算温度:温度(单位为°C)={(VSENSE-V25)/Avg_Slope}+25

其中:

-V25=25°C时的VSENSE

-Avg_Slope=温度与VSENSE曲线的平均斜率(以mV/°C或μV/°C表示)

有关V25和Avg_Slope实际值的相关信息,请参见数据手册中的电气特性一节。

注意:传感器从掉电模式中唤醒需要一个启动时间,启动时间过后其才能正确输出VSENSE。ADC在上电后同样需要一个启动时间,因此,为尽可能减少延迟间,应同时将ADON和TSVREFE位置1。

温度传感器的输出电压随温度线性变化。由于工艺不同,该线性函数的偏移量取决于各个芯片(芯片之间的温度变化可达45°C)。    

内部温度传感器更适用于对温度变量而非绝对温度进行测量的应用情况。如果需要读取精确温度,则应使用外部温度传感器。    

通过以上讲解,即可总结一下CKS32内部温度传感器使用的步骤了,如下:

1)设置ADC,开启内部温度传感器    

关于如何设置ADC,之前已经介绍了,我们采用与常规ADC配置相似的设置。不同的是之前温度传感器读取外部通道的值,而内部温度传感器相当于把通道端口连接在内部温度传感器上,所以这里,我们要开启内部温度传感器功能:

ADC_TempSensorVrefintCmd(ENABLE);

2)读取通道16的AD值,计算结果    

在设置完之后,即可读取温度传感器的电压值了,得到该值后,利用上面的公式计算温度值。如文中所述,可知ADC通道与GPIO的对应关系,内部温度传感器是通过对应ADC通道16,其它的跟上一节的讲解是一样的。

软件设计

打开内部温度传感器实验工程,即可看到adc.c与adc.h文件。函数的作用跟之前ADC实验基本是一样的。不同的是在adc_Init函数中设置为开启内部温度传感器模式,代码如下:

voidadc_Init(void)
{
    ADC_CommonInitTypeDefADC_CommonInitStructure;
    ADC_InitTypeDef       ADC_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE); 
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; 
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
    ADC_CommonInit(&ADC_CommonInitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfConversion = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    ADC_TempSensorVrefintCmd(ENABLE);
    ADC_Cmd(ADC1, ENABLE);
}

这部分代码与之前的ADC测试代码类似,仅去掉外部IO初始化。

我们还在adc.c里面添加了Get_Temprate函数,用来获取温度值,也就是把采集到的电压根据计算公式转换为温度值,Get_Temprate函数代码如下:

short Get_Temprate(void)
{
    u32 adcx;  
    double temperate;
    ADC_SoftwareStartConv(ADC1);
    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//µÈ´ýת»»½áÊø
    ADC_RegularChannelConfig(ADC1,ADC_Channel_16,1,ADC_SampleTime_480Cycles );
    adcx = ADC_GetConversionValue(ADC1);
    temperate=(float)adcx*(3.3/4096);
    temperate=(temperate-0.76)/0.0025 + 25;
    printf("温度:%f ℃\r\n",temperate);
}

接下来我们就可以开始读取温度传感器的电压了。在main.c文件里面我们的main函数代码如下:

int main(void)
{
    u8 len,t;
    u16 parse_num;
    float temp,temperate;
    delay_init(168);      
    uart_init(115200);
    adc_Init();
    while(1)
    {
        Get_Temprate();
        printf("\r\n\r\n");
        delay_ms(500);
    }
}

这里同上一章的主函数也大同小异,上面的代码将温度传感器得到的电压值,换算成温度值,下载后通过串口打印结果进行验证。

6.png

以上即为ADC内部温度传感器的实验内容。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 13

DAC模块作为CKS32F4xx系列的一个常用外设,可以将数字信号转换成模拟信号,最高分辨率可达12位,且两个独立DAC输出通道转换互不影响,各个通道均能使用DMA功能,可由软硬件触发。因此,为了实现DAC输出正弦波,拟采用一定的时间向DAC的数据寄存器写入数据,随后进行数模转换输出不同的电压,最后在时间轴上显示出波形。同时为了不占用CPU资源,配置DMA建立传输通道,以便数据快速的从内存搬移到外设。且在DAC初始化时,可以设置成定时器触发,待定时器溢出就会触发DAC工作,所以只要修改定时器的定时时间,就可改变正弦波周期。

DAC简述

1.png

①:DAC将VREF+引脚作为参考电压,在实际使用时将VSSA接地,同时把VREF+和VDDA接3.3V,DAC即可获得0~3.3V的输出电压。

②:数模转换器以VREF+作为参考电源,将DAC的数据寄存器“DORx”的数字编码转换成模拟信号并由右侧的“DAC_OUTx”通道输出。在CKS32有2个这样的DAC部件,其中PA4对应通道1,PA5对应通道2。

③:控制逻辑可以控制数据寄存器“DORx”加入一些伪噪声信号或配置产生三角波信号。

④:使用DAC时,数据会被先写入到DHRx寄存器,随后DAC会根据触发配置进行处理,最后将数据传输至DORx。DAC的触发源有三种,分别为:外部中断源触发、定时器触发和软件控制触发。

对于单DAC通道x的三种数据格式

8位数据右对齐:

用户须将数据写入寄存器DAC_DHR8Rx[7:0]位(实际是存入寄存器DHRx[11:4]位)。

12位数据左对齐:

用户须将数据写入寄存器DAC_DHR12Lx[15:4]位(实际是存入寄存器DHRx[11:0]位)。

12位数据右对齐:

用户须将数据写入寄存器DAC_DHR12Rx[11:0]位(实际是存入寄存器DHRx[11:0]位)。

数字输入经过DAC被线性地转换为模拟电压输出,任一DAC通道引脚上的输出电压满足下面的关系:

本案例中选择DAC的通道1,并采用12位的右对齐方式,通过查阅《CKS32F4xx参考手册》DAC和DMA章节可知,DAC1对应DMA1控制器通道7数据流5。

总的来说,DAC的输出是由DORx寄存器直接控制的,而用户写的数据是要写入DHRx寄存器,然后通过DHRx间接操作DORx,最终实现DAC的输出。

DAC输出正弦波配置

本文采用DAC1+TIM2+DMA1的方式,通过TIM2触发DAC1转换,转换完成后通过DMA1输出,主要步骤如下:

①由Matlab计算一个周期的正弦波数组;

②根据一个正弦波周期内点数和所需正弦波频率确定定时器触发间隔;

③初始化DAC1输出管脚和工作模式;

④配置触发DAC1用的定时器2;

⑤配置DMA1自主搬运正弦波数组。

待上述配置完成后,将PA4引脚接到示波器上,即可显示正弦波。以下是DAC的详细配置。

(1)正弦波数组生成

以下代码用于生成正弦波波形表:

for(i=0;i<100;i++)
{
    Sine12bit[i]=2048*sin(1.0*i/(100- 1)*2*PI)+2048;
}

从上述函数可以看出,正弦波的幅度被控制在0~4096之间,一个周期被平均分成100份,即100个点代表一个周期的波形,数组Sine12bit里面是100个采样点。

const uint16_t Sine12bit[100] = {

0x0800,0x0881,0x0901,0x0980,0x09FD,0x0A79,0x0AF2,0x0B68,0x0BDA,0x0C49,0x0CB3,0x0D19,0x0D79,0x0DD4,0x0E29,0x0E78,0x0EC0,0x0F02,0x0F3C,0x0F6F,0x0F9B,0x0FBF,0x0FDB,0x0FEF,0x0FFB,0x0FFF,0x0FFB,0x0FEF,0x0FDB,0x0FBF,0x0F9B,0x0F6F,0x0F3C,0x0F02,0x0EC0,0x0E78,0x0E29,0x0DD4,0x0D79,0x0D19,0x0CB3,0x0C49,0x0BDA,0x0B68,0x0AF2,0x0A79,0x09FD,0x0980,0x0901,0x0881,0x0800,0x077F,0x06FF,0x0680,0x0603,0x0587,0x050E,0x0498,0x0426,0x03B7,0x034D,0x02E7,0x0287,0x022C,0x01D7,0x0188,0x0140,0x00FE,0x00C4,0x0091,0x0065,0x0041,0x0025,0x0011,0x0005,0x0001,0x0005,0x0011,0x0025,0x0041,0x0065,0x0091,0x00C4,0x00FE,0x0140,0x0188,0x01D7,0x022C,0x0287,0x02E7,0x034D,0x03B7,0x0426,0x0498,0x050E,0x0587,0x0603,0x0680,0x06FF,0x077F};

(2)GPIO和DAC模式配置

该部分为输出引脚配置和DAC通道1配置,代码如下:

void DAC1_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    DAC_InitTypeDef DAC_InitStructure;
    /*  Enable GPIOA clock  */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);/*  Enable DAC clock  */RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);/*  Configure the DAC Pin to Analog mode: DAC_OUT1 -- PA4  */GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;  
    GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /*  Configure DAC Channel_1  */
    DAC_InitStructure.DAC_Trigger= DAC_Trigger_T2_TRGO;
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    DAC_Cmd(DAC_Channel_1, ENABLE);//Enable DAC Channel_1.
    DAC_DMACmd(DAC_Channel_1, ENABLE);//Enable DAC channel_1 DMA request.
}

在DAC1_GPIO_Init函数中,实现了相应GPIO引脚(PA4)的初始化和DAC工作模式配置。其中为了避免寄生的干扰和额外的功耗,应将PA4引脚设置成模拟输入模式(AIN),如此方可正常工作。

而对DAC工作模式进行配置时,可查看CKS官方提供的DAC_InitTypeDef结构体,该结构体中主要包含了DAC_CR寄存器的各寄存器配置。如下是DAC_InitTypeDef结构体成员简述:

(a)DAC_Trigger

该成员用于DAC的触发模式配置,由上文DAC通道框图可知,共有三种触发模式,分别是定时器触发(DAC_Trigger_T2/4/5/6/7/8_TRGO)、软件触发(DAC_Trigger_Software)和EXTI_9触发方式(DAC_Trigger_Ext_IT9)。

(b)DAC_WaveGeneration

该成员可配置输出伪噪声和三角波输出(DAC_WaveGeneration_Noise/Triangle),若使用自定义输出,应配置为DAC_WaveGeneration_None。

(c)DAC_OutputBuffer

该成员用于控制是否使能DAC的输出缓冲(DAC_OutputBuffer_Enable/Disable)。若需要直接驱动外部负载,可以使能该成员以减小输出阻抗。

(d)DAC_LFSRUnmask_TriangleAmplitude

该成员通过控制DAC_CR的MAMP2位设置LFSR寄存器位的数据,即当使用伪噪声或三角波输出时要叠加到DHRx的值。若使用伪噪声输出时LFSR=0xAAA,这时该结构体成员可赋值为DAC_LFSRUnmask_Bit0~DAC_LFSRUnmask_Bit11_0;若使用三角波输出时,这时该结构体成员可赋值为DAC_TriangleAmplitude_1~DAC_TriangleAmplitude_4096,可用于设置三角波的最大幅值。

本例中,将DAC通道1配置成定时器TIM2触发,不使用波形发生器和不使用输出缓存,不使用输出缓存是因为CKS32的DAC无需外部运放就可以直接驱动负载,三角波振幅一项虽然本案例没有用到,可以配置成任意,但此项不可缺,最后调用DAC_Cmd、DAC_DMACmd函数使能DAC通道1和DMA的请求。

(3)定时器配置

该部分是配置触发DAC的定时器TIM2,通过设定触发的间隔,从而间接控制正弦波周期,TIM2的工作决定DMA与DAC的工作频率,代码如下:

void TIM2_Init(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    /*  Enable Timer2 clock.  */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);/*  Configure Timer2 --Clock Frequency is 84MHz  */
    TIM_TimeBaseStructure.TIM_Period=83; TIM_TimeBaseStructure.TIM_Prescaler= 0x0;       
    TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;    
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);  /*  Configure the trigger source for Timer2.  */TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
    TIM_Cmd(TIM2, ENABLE);//Enable Timer2.
}

前文的DAC已选用TIM2当触发源,此处TIM2的定时周期被配置为83,向上计数,不分频。CKS32F4xx系列的主频是168MHz,TIM2的时钟是84MHz,所以TIM2的更新频率是84M/(TIM_Period+1)/(TIM_Prescaler+1),即TIM2每隔1us触发一次DAC事件,不需要设置中断,当定时器向上计数至指定值时,产生Update事件,同时触发DAC把DHRx寄存器的数据转移到DORx,开始进行转换。由于正弦波数组是100个采样点,可得正弦波的输出频率为:

(4)DMA配置

该部分主要完成数据的传输,代码如下:

void DMA_InitForDAC(void)
{
    DMA_InitTypeDef  DMA_InitStructure;
    /*  Enable DMA1 clock.  */RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);/*  Configure DMA1 Stream5 Channel_7 For DAC1  */
    DMA_InitStructure.DMA_Channel = DMA_Channel_7;  DMA_InitStructure.DMA_PeripheralBaseAdDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)Sine12bit ;DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_BufferSize = 100;
    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_FIFOMode = DMA_FIFOMode_Disable;      DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA1_Stream5, &DMA_InitStructure);DMA_Cmd(DMA1_Stream5, ENABLE); //Enable DMA1 Stream5.
}

需要注意的是,DAC->DHR12R1对应数据寄存器的地址,正弦波数组Sine12bit对应数据输入地址,DMA缓存的个数是单个正弦波周期对应的点数,DMA需工作在循环模式,由于正弦波数组Sine12bit定义为16位,那么涉及数据传输的变量都要配置成半字16位。经过上述的配置后,定时器TIM2每隔1us就会触发DMA搬运正弦波数组的一个数据到DAC通道1寄存器进行转换,每搬运100个数据即一个完整周期后,DMA开始循环,最终循环输出正弦波。

(5)主函数配置

本例程主函数主要对前文所述函数依次调用,程序编译下载至开发板,使用示波器测量PA4引脚即可查看输出10kHz的正弦波形,代码如下:

int main(void)   
{
    DAC1_GPIO_Init();
    TIM2_Init();
    DMA_InitForDAC();
    while (1);
}

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 30

页面

订阅 RSS - CKS32F4xx