在嵌入式系统中,主控MCU集成多种片上外设设计

电子说

1.2w人已加入

描述

在嵌入式系统中,主控MCU往往集成了多种片上外设,例如,GPIO、PWM、I²C、SPI、UART、ADC等等。通过它们可以使MCU轻松的与“外部世界”相互连接、相互通信,从而与“外部世界”交互。在AWorks中,对这些外设进行了抽象,为同一类外设提供了相同的使用接口。对于用户来讲,无论使用什么MCU,只要该MCU具有相应的外设,则均可以使用相同的外设接口进行操作,因而使用某一外设的应用程序可以轻松的跨平台(移到另外一个硬件平台)复用。同时,由于直接为用户提供了外设的使用接口,因此,用户不再需要了解底层的寄存器操作,由此可以将用户从阅读寄存器手册、编写底层驱动中解脱出来,使用户更加专注于产品本身的“核心域”,提升产品的竞争力。

7.1  GPIO

在使用一个I/O口前,必须正确的配置I/O的功能和模式。同时,由于一个I/O口往往可用于多种功能,但同一时刻只能用于某一确定的功能,为了避免冲突,在将一个引脚用作某一功能前,应该向系统申请,使用完毕后再释放。

当将I/O口用作GPIO(General Purpose Input Output)功能时,GPIO作为一种通用输入/输出接口,有两种常用的使用方式:一种是用作普通的输入/输出接口;一种是用作中断输入接口,即当指定的输入状态事件发生(比如:下降沿)时,触发用户自定义回调函数。

7.1.1  I/O配置

通常情况下,一个引脚往往可以用作多种功能,例如,在i.MX280中,PIO3_0可以用作下面4种功能:

  • 应用串口0的接收引脚;

  • I²C0的时钟引脚;

  • 调试串口的CTS引脚;

  • 通用GPIO。

同时,GPIO往往还具有多种模式,比如:上拉、下拉、开漏、推挽等。为了正确使用一个IO口,必须先将其配置为正确的功能和模式。AWorks提供了I/O配置接口,其原型为:

嵌入式

其中,pin为引脚编号,flags为配置的功能和模式标志,返回值为标准的错误号,返回AW_OK时表示配置成功,否则,表示配置失败,配置失败可能是由于部分功能和模式不支持造成的。

引脚编号pin用于指定需要配置的引脚,在GPIO标准接口层中,所有函数的第一个参数均为pin,用于指定具体操作的引脚,具体的引脚编号在{chip}_pin.h文件中定义(chip代表芯片名,例如,对于i.mx28x系列MCU,chip即为imx28x,对应文件名为imx28x_pin.h),宏名的格式为PIOx_y,比如:PIO3_0。

flags为配置标志,由“通用功能|通用模式|平台功能|平台模式”(‘|’就是C语言中的按位或)组成。

1.  通用功能和模式

通用功能和模式是AWorks抽象的GPIO最通用的功能和模式,它们在aw_gpio.h文件中使用宏的形式进行了定义,宏名格式为AW_GPIO_*。通用功能相关宏定义与含义详见表7.1,通用模式相关宏定义与含义详见表7.2。

表7.1 引脚通用功能嵌入式

表7.2 引脚通用模式嵌入式

通用功能和模式相关的宏定义在标准接口层中,不会随芯片的改变而改变,使用通用功能和模式的应用程序是与具体芯片是无关的,可以跨平台复用。使用通用功能和模式配置引脚的范例程序详见程序清单7.1。

程序清单7.1 使用通用功能和模式配置引脚的范例程序嵌入式

2.  平台功能和模式

平台功能和模式与具体芯片相关,会随着芯片的不同而不同,主要包括引脚的复用功能和一些特殊的模式,平台功能和模式相关的宏定义在{chip}_pin.h文件中,宏名的格式为PIOx_y_*,比如:PIO3_0_AUART0_RX,即PIO3_0的串口0接收引脚。

如需查找某一引脚(比如:PIO3_0)相关的平台功能和平台模式,可以打开{chip}_pin.h文件,找到以PIO或引脚编号PIO3_0为前缀的所有宏定义。若一个宏定义以引脚编号为前缀,则表明该宏仅可用于引脚编号对应的引脚,例如,PIO3_0_AUART0_RX仅可用于PIO3_0。若一个宏定义没有以具体引脚编号为前缀,仅以PIO作为前缀,则表明该宏对应的功能或模式可用于所有引脚。例如,PIO3_0相关的平台功能详见表7.4,相关的平台模式定义详见表7.3。

表7.3 引脚平台模式嵌入式

表7.4 PIO3_0平台功能嵌入式

在表7.3中,所有平台模式都以PIO作为前缀,而非PIO3_0作为前缀。表明这些平台模式不是PIO3_0引脚所特有的,而是所有引脚都可能支持的模式,即这些模式对应的宏可以在配置任意引脚时使用。使用平台功能和模式配置引脚的范例程序详见程序清单7.2。

程序清单7.2 使用通用功能和模式配置引脚的范例程序嵌入式

特别地,平台功能和模式可以和通用功能和模式组合使用。例如,在配置I²C引脚时,可以开启内部上拉,范例程序详见程序清单7.3。

程序清单7.3 通用模式和平台模式混合使用

嵌入式

7.1.2  I/O的申请和释放

如表7.4所示,PIO3_0可以被用作4种功能。虽然PIO3_0的功能众多,但是,在同一时刻,其只能被用作某一确定功能,并不能同时使用多种功能。

随着系统复杂度的上升,用户往往很难保证某一引脚只被用作一种功能,稍有不慎,就可能将某一引脚同时配置为多种功能,而实际上,由于引脚同一时刻只能被用作一种功能,因此,部分使用某种功能的程序将不能正常工作。这种情况下,用户往往很难发现工作异常的原因。

例如,PIO3_0已经被用作I²C0的时钟引脚,结果由于程序员的不小心,又将其配置为串口0的接收引脚,此时,若不加以保护,则引脚会被配置为串口0的接收引脚,这将导致I²C0工作不正常。

为了保证引脚功能的互斥使用,AWorks提供了一种申请机制,在将引脚用作某一功能前,必须向系统申请,若该引脚处于空闲状态,还未被申请过,则本次申请成功,同时标记该引脚已被使用。若在申请时,该引脚已被申请,则本次申请失败。当一个引脚使用完毕时,应该释放该引脚,以便系统将该引脚分配给下一个申请者。

在实际应用中,往往在设计硬件电路时,一个引脚只会被用作某一确定的功能,不会将一个引脚同时用作多个外设功能引脚,因此,软件设计正确无误的情况下,通常不会出现申请失败的情况。当申请失败时,往往是由于软件开发人员的错误导致的,这就需要软件开发人员引起足够的重视,出现申请失败的情况时,必须立即解决,找到某一引脚被非法占用的位置。

引脚申请机制包括两个接口,相关接口的原型详见表7.5。

表7.5 引脚申请机制相关接口(aw_gpio.h)嵌入式

1.  申请引脚

当需要使用一组引脚时,应该向系统申请,申请的函数原型为:

嵌入式

其中,p_name为申请者的名字,p_pins指向引脚列表,num为本次申请的引脚个数。返回值为标准的错误号,返回AW_OK时表示申请成功,可以正常使用相关的引脚,否则,表示申请失败,相关引脚已经被占用,无法使用。

p_name为申请者的名字,其指向一个字符串,例如,"LED"、"UART" 、"SSP0"、"I²C0"等,名字仅作标记,便于引脚的跟踪,可以任意设定。

p_pins为引脚列表,例如,要申请PIO3_2和PIO3_3这两个引脚用作串口功能,则引脚列表可以定义为:

嵌入式

num表示本次申请的引脚个数,其值应该与p_pins指向的列表中的引脚个数相等,比如:2。申请PIO3_2和PIO3_3这两个引脚用作串口功能的范例程序详见程序清单7.4。

程序清单7.4 引脚申请范例程序

嵌入式

只有当引脚申请成功后,才能配置相应的引脚用作串口功能。

特别地,即使将一个IO口用作通用的GPIO口,也需要先申请,例如,要将PIO2_6用作通用I/O口,用以控制LED0。则同样需要在将其配置为通用I/O功能前向系统申请,范例程序程序清单7.4。

嵌入式

关于需要申请引脚的时机,一个简单的原则就是:在调用aw_gpio_pin_cfg()前,都应该确保已经向系统申请到该引脚的使用权。

2.  释放引脚

当引脚使用完毕,暂时不再使用时,应该释放引脚,清除每个引脚的使用记录。便于其它模块申请使用。其函数原型为:

嵌入式

其中, p_pins指向引脚列表,num为本次释放的引脚个数。返回值为标准的错误号,返回AW_OK时表示释放成功,否则,表示释放失败,可能是由于参数错误导致的。

p_pins为引脚列表,其应该与申请引脚时的引脚列表一致,例如,要释放之前申请的PIO3_2和PIO3_3这两个引脚,则引脚列表定义为:

嵌入式

num表示本次释放的引脚个数,其值应该与p_pins指向的列表中的引脚个数相等,比如:2。释放PIO3_2和PIO3_3这两个引脚的范例程序详见程序清单7.5。

程序清单7.5 释放引脚范例程序嵌入式

在释放引脚前,可以将I/O口配置为通用GPIO功能,以使其不再作为串口的功能引脚。特别注意,这一配置操作只能在释放前完成,释放后将失去引脚的控制权,不可再对其进行配置操作。

7.1.3  普通I/O接口

当将一个引脚配置为通用I/O接口时(输入或输出),则可以通过相关的I/O接口控制其输出状态或获取其输入状态。相关接口的原型详见表7.6。

表7.6 GPIO通用接口(aw_gpio.h)嵌入式

1.  获取引脚电平

无论当前引脚处于输出模式还是输入模式,都可以通过该接口获取当前的引脚电平状态。其函数原型为:

嵌入式

其中的pin为引脚编号。返回值为标准的错误号,若返回值小于0,则表示获取失败,失败的原因可能是用于引脚不存在。若返回值为非负数,则表明获取成功,其值为0时,表明当前引脚的电平为低电平,其值大于0时,表明当前引脚的电平为高电平。范例程序详见程序清单7.6。

程序清单7.6 获取引脚电平范例程序嵌入式

2.  设置引脚电平

当引脚处于输出模式时,可以通过该接口设置引脚的输出电平,其函数原型为:

嵌入式

其中,pin为引脚编号。value为设置的值,当value为0时,输出低电平,否则,输出高电平。返回值为标准的错误号,返回AW_OK时表示设置成功,否则,表示设置失败。

例如,可以控制GPIO的输出电平,达到控制LED状态的目的。在EPC-AW280上板载了一个运行指示灯,标识为RUN,其对应的原理图详见图7.1。

嵌入式

图7.1 板载LED电路

其中,发光二极管的阴极与MCU的PIO2_6相连,当I/O输出低电平时,由于LED阳极加了3.3V电压(高电平1),因而形成了电位差,所以有电流流动,则LED发光二极管导通,即LED发光;当I/O输出高电平1时,由于无法形成电位差,则LED二极管不导通,即LED熄灭。

基于此,可以使PIO2_6输出低电平,以点亮LED,范例程序详见程序清单7.7。

程序清单7.7 设置引脚电平范例程序嵌入式

程序中,首先将PIO2_6配置为了输出模式,并将初始值设定为高电平,以便初始化完毕后,LED默认处于熄灭状态。

同理,点亮LED后,可以控制PIO2_6输出高电平,以熄灭LED。如以一定的频率交替使PIO2_6输出高电平和低电平,则可以看到LED闪烁,范例程序程序清单7.8。

程序清单7.8 交替输出高低电平的范例程序嵌入式

这里通过GPIO的输出控制LED,仅为介绍GPIO接口的使用方法。实际中,AWorks已经定义了通用的LED接口,在应用中操作LED时,均建议使用通用的LED接口。

3.  翻转引脚电平

该接口用于翻转 GPIO 引脚的输出电平,如果GPIO当前输出的是低电平,当调用该函数后,GPIO将输出高电平,反之则输出变为低电平。其函数原型为:

嵌入式

其中,pin为引脚编号。返回值为标准的错误号,返回AW_OK时表示翻转成功,否则,表示翻转失败。在程序清单7.8中,通过交替输出高电平和低电平实现了LED闪烁。而交替输出高低电平本质上就是不断翻转GPIO的输出电平。基于此,若以一定的频率翻转GPIO的输出电平,同样可以实现LED闪烁,范例程序详见程序清单7.9。

程序清单7.9 翻转引脚电平的范例程序嵌入式

7.1.4  中断I/O接口

由对普通I/O接口的介绍可知,若需获取某一引脚的状态,可以通过aw_gpio_get()接口得到引脚的当前输入状态。若用户需要监控GPIO某一状态事件(比如:GPIO由高电平变为低电平),即当出现某一状态时,才执行相应的操作。若还是使用普通I/O接口,则必须不断的调用aw_gpio_get()接口,直到读取到引脚电平为0。显然,由于使用这种方式需要不断的轮询,因而是非常消耗CPU的。为此,AWorks提供了中断I/O接口,其可以使GPIO工作在中断状态,实时监控引脚的输入状态,只有当用户期望的状态发生时,才通知用户,用户进而执行相关的处理,当期望的状态没有发生时,则用户完全不用关心引脚的状态,无需对引脚进行不断的轮询。

AWorks为I/O中断相关的操作定义了一套触发接口,用户期望的状态称之为触发条件,当GPIO的输入状态满足触发条件时,将自动调用引脚触发回调函数,以通知用户作相关的处理。相关接口原型详见表7.7。

表7.7 GPIO触发相关接口函数嵌入式

1.   连接引脚触发回调函数

在使用引脚的触发功能前,应该首先连接一个回调函数到触发引脚,当相应引脚的触发事件产生时,则会调用本函数连接的触发回调函数。其函数原型为:

嵌入式

其中,pin为引脚编号,pfunc_callback为指向回调函数的指针,其指向的函数即为本次连接的回调函数,p_arg为传递给回调函数的用户参数。返回值为标准的错误号,返回AW_OK时表示连接成功,否则,表示连接失败,失败可能是由于该引脚不支持触发模式。

pfunc_callback为指向回调函数的指针,其类型为aw_pfuncvoid_t,该类型在aw_types.h文件中定义如下:

嵌入式

由此可见,回调函数的类型是无返回值,具有一个void *类型参数的函数。当触发事件发生时,将自动调用pfunc_callback指向的函数,并将连接触发回调函数时设定的p_arg作为回调函数的参数。例如,要将引脚PIO3_6用作触发功能,则首先需要连接触发回调函数,范例程序详见程序清单7.10。

程序清单7.10 连接引脚触发回调函数范例程序嵌入式

2.  断开引脚触发回调函数

与aw_gpio_trigger_connect()函数的功能相反,当一个引脚不再需要用作触发功能时,可以断开引脚与回调函数的连接。或者当需要将一个引脚的回调函数重新连接到另外一个函数时,则应该先断开当前连接的回调函数,再重新连接到新的回调函数。其函数原型为:

嵌入式

其中,pin为引脚编号,pfunc_callback 为指向回调函数的指针,其值应该与连接回调函数时设定的回调函数一致,p_arg为回调函数的参数,其值同样应该与连接回调函数时设定的p_arg一致。返回值为标准的错误号,返回AW_OK时表示断开连接成功,否则,表示断开连接失败,失败可能是由于参数错误造成的。使用范例详见程序清单7.11。

程序清单7.11 断开引脚触发回调函数范例程序嵌入式

3.  配置引脚触发条件

连接触发回调函数后,需要配置引脚触发的条件,其函数原型如下:

嵌入式

其中,pin为引脚编号,flags用于指定触发条件。返回值为标准的错误号,返回AW_OK时表示配置成功,否则,表示配置失败,配置失败可能是由于该引脚不支持配置的触发条件。

触发条件决定了引脚触发的时机,所有可选的触发条件详见表7.8。

表7.8 GPIO触发条件配置宏嵌入式

实际中,并不是每一个引脚都支持表中所有的触发条件,当配置触发条件时,应检测返回值,确保相应引脚支持所配置的触发条件。特别地,部分引脚可能不支持触发模式,配置任何触发条件都会失败。

例如,配置PIO3_6为触发模式,触发条件为下降沿触发,范例程序详见程序清单7.12。

程序清单7.12 配置触发条件的范例程序嵌入式

4.  打开引脚触发

当正确连接回调函数并设置相应的触发条件后,可以打开引脚触发,使引脚触发开始工作。其函数原型为:

嵌入式

其中,pin为引脚编号,返回值为标准的错误号,返回AW_OK时表示打开成功,否则,表示打开失败,打开失败可能是由于未正确连接回调函数或设置触发条件。

综合连接引脚触发回调函数和配置引脚触发条件的接口,可以实现一个完整的引脚触发范例程序,详见程序清单7.13。

程序清单7.13 打开引脚触发范例程序嵌入式

程序中,由于需要使用aw_gpio_pin_cfg()将引脚配置为输入模式,因此,在配置引脚前,同样先向系统申请了引脚的控制权。特别地,在aw_gpio_trigger_cfg()前,无需再次申请。

程序运行后,若PIO3_6引脚出现下降沿(可以通过导线将PIO3_6与GND轻触几次),则可以观察到LED的状态发生变化。实际中,若采用轻触GND的方式产生下降沿,由于轻触时会有抖动,可能轻触时将在短时间内产生多个下降沿,此时,可能观察到LED无规律的翻转。

注意,必须先连接回调函数,再配置引脚的触发条件,这个顺序不能颠倒,即不能颠倒程序清单7.13中第18行和第19行的顺序。

特别地,当将引脚用作触发功能时,往往需要将引脚配置为输入模式。程序清单7.13的第14行程序将引脚配置为了输入模式,并开启了上拉,使得PIO3_6在外部悬空状态时,仍能处于确定的高电平状态。

5.  关闭引脚触发

当暂时不使用引脚触发功能时,可以关闭引脚触发,引脚触发将停止工作,此时,即使满足配置的触发条件,也不会触发调用引脚相应的回调函数。其函数原型为:

嵌入式

其中,pin为引脚编号,返回值为标准的错误号,返回AW_OK时表示关闭成功,否则,表示关闭失败。使用范例详见程序清单7.14。

程序清单7.14 关闭引脚触发范例程序嵌入式

当引脚触发被关闭后,若需引脚触发重新开始工作,可以使用aw_gpio_trigger_on()再次打开引脚触发功能。

7.2  PWM

7.2.1  PWM简介

大小和方向随时间发生周期性变化的电流称为交流,交流中最基本的波形称为正弦波,除此之外的波形称为非正弦波。计算机、电视机、雷达等装置中使用的信号称为脉冲波、锯齿波等,其电压和电流波形都是非正弦交流的一种。

PWM(Pulse Width Modulation)就是脉冲宽度调制的意思,一种脉冲编码技术,即可以按照信号电平改变脉冲宽度。而脉冲宽度调制波的周期也是固定的,用占空比(高电平/周期,有效电平在整个信号周期中的时间比率,范围为0~100%)来表示编码数值。PWM可以用于对模拟信号电平进行数字编码,也可以通过高电平(或低电平)在整个周期中的时间来控制输出的能量,从而控制电机转速或LED亮度。

PWM信号是由计数器和比较器产生的,比较器中设定了一个阈值,计数器以一定的频率自加。当计数器的值小于阈值时,则输出一种电平状态,比如,高电平。当计数器的值大于阈值,则输出另一种电平状态,比如,低电平。当计数器溢出清0时,又回到最初的电平状态,即I/O引脚发生了周期性的翻转而形成PWM波形,详见图7.2。

嵌入式

图7.2 PWM波形图

当计数器的值小于阈值时,则输出高电平;当计数器的值大于阈值时,则输出低电平。阈值为45,计数器的值最大为100。PWM波形有三个关键点:起始点①,此时计数器的值为0;计数器值达到阈值②,I/O状态发生翻转;计数器达到最大值③,I/O状态发生翻转,计数器的值回到0重新开始计数。

7.2.2  PWM接口

AWorks提供了接口函数,用于控制PWM输出,接口原型详见表7.9。

表7.9 PWM接口函数(aw_pwm.h)嵌入式

1.  配置PWM通道

配置一个PWM通道的周期时间和高电平时间,其函数原型为:

嵌入式

其中,pid为PWM通道的id号,duty_ns为脉宽时间(单位:ns),period_ns为周期时间(单位:纳秒)。返回值为标准的错误号,返回AW_OK时表示配置成功,否则,表示配置失败。

在AWorks中,一个系统往往可以输出多个通道的PWM,各个通道通过pid区分,一个系统实际可以输出PWM的通道数与具体硬件平台相关。例如,在i.MX28x系统中,可以输出8路PWM。则PWM通道的编号为:0 ~ 7,各通道对应的默认I/O口详见表7.10。

表7.10 各通道对应的I/O口嵌入式

注:实际中,各个PWM通道对应的I/O口是可以配置的,具体配置方法详见SDK配套资料中的《用户手册.pdf》。

输出PWM的频率由周期时间period_ns决定,例如,需要输出1KHz的PWM,周期时间为1ms,则period_ns的值为1000000。

输出PWM的脉宽由duty_ns决定,它和周期值决定了输出PWM的占空比,占空比即为:duty_ns / period_ns。duty_ns的值不能超过period_ns。配置PWM通道0输出PWM的频率为1KHz,占空比为50%的范例程序详见程序清单7.15。

程序清单7.15 配置PWM通道的范例程序嵌入式

2.  使能PWM输出

PWM通道配置完成后,并不会输出PWM。只有使能通道输出后,才能使相应通道开始输出PWM,其函数原型为:

嵌入式

其中,pid为PWM通道的id号。返回值为标准的错误号,返回AW_OK时表示使能成功,PWM开始输出,否则,表示使能失败,PWM不能正常输出,失败原因可能是通道号设置有误。使能PWM通道0输出的范例程序详见程序清单7.16。

程序清单7.16 使能PWM输出范例程序嵌入式

3.  禁能PWM输出

若需关闭PWM通道的输出,则可以禁能相应PWM通道,其函数原型为:

嵌入式

其中,pid为PWM通道的id号。返回值为标准的错误号,返回AW_OK时表示禁能成功,PWM停止输出,否则,表示禁能失败。禁能PWM通道0输出的范例程序详见程序清单7.17。

程序清单7.17 禁能PWM输出范例程序嵌入式

EPC-AW280上板载了一个无源蜂鸣器,其对应的原理图详见图7.3,蜂鸣器的控制引脚与GPIO3_29连接,当GPIO3_29输出高电平时,则三极管导通,向蜂鸣器供电;当GPIO3_29输出低电平时,则三极管截止,停止向蜂鸣器供电。对于无源蜂鸣器,只要以一定的频率控制蜂鸣器的“通电”和“断电”,即可使蜂鸣器产生机械振动音,声音的频率与控制信号的频率相同,只要频率在人耳的听觉范围内,即可听到蜂鸣器发出的声音。

嵌入式

图7.3 板载蜂鸣器电路

基于此,可以通过GPIO3_29输出一定频率的PWM,使蜂鸣器发声。由表7.10可知,GPIO3_29对应的PWM通道为4。因此,控制PWM通道4输出PWM即可听到蜂鸣器发声。范例程序详见程序清单7.18。

程序清单7.18 使用PWM输出驱动蜂鸣器发声范例程序嵌入式

运行程序,可以听到蜂鸣器发出的声音。由于程序中一直输出PWM,因此蜂鸣器会一直鸣叫,若仅需蜂鸣器每秒“嘀”一声,则可以在不需要发声时禁能PWM输出。范例程序详见程序清单7.19。

程序清单7.19 使用PWM控制蜂鸣器每秒“嘀”一声的范例程序嵌入式

这里通过控制PWM的输出驱动蜂鸣器发声,仅为介绍PWM接口的使用方法。实际中,AWorks已经定义了通用的蜂鸣器接口,在应用中操作蜂鸣器时,均建议直接使用通用的蜂鸣器接口。

7.3  SPI总线

7.3.1  SPI总线简介

SPI(Serial Peripheral Interface)是一种全双工同步串行通信接口,常用于短距离高速通信,其数据传输速率通常可达到几M甚至几十M。SPI通信采用主/从结构,主/从双方通信时,需要使用到4根信号线:SCLK、MOSI、MISO、CS。其典型的连接示意图详见图7.4。

嵌入式图7.4 SPI连接示意图——单从机

  • SCLK:时钟信号,由主设备产生;

  • MOSI:主机数据输出,从机数据输入;

  • MISO:从机数据输出,主机数据输入;

  • CS:片选信号,由主设备控制。

数据传输是由主机发起的,主机在串行数据传输前驱动CS信号,使之变为有效状态(通常情况下,有效状态为低电平),接着,在SCLK上输出时钟信号,在时钟信号的同步下,每个时钟传输一位数据,主机数据通过MOSI传输至从机,从机数据通过MISO传输至主机,数据传输完毕后,释放CS信号,使之变为无效状态,一次数据传输完成。

一个主机可以连接多个从机,多个从机共用SCLK 、MOSI、MISO三根信号线,每个从机的片选信号CS是独立的,因此,若主机需要连接多个从机,就需要多个片选控制引脚。连接示意图详见图7.5。当一个主机连接多个从机时,同一时刻最多只能使一个片选信号有效,以选择一个确定的从机作为数据通信的目标对象。也就是说,在某一时刻,最多只能激活寻址一个从机,以使各从机之间相互独立的使用,互不干扰。

嵌入式

图7.5 SPI连接示意图——多从机

注意,单个通信网络中,可以有多个从机,但有且只能有一个主机。

除了需要了解上述SPI的基本概念外,读者还应该理解SPI的传输模式,以便在操作SPI从机器件时,可以正确的设置SPI主机的传输模式。

SPI数据传输是在片选信号有效时,数据位在时钟信号的同步下,每个时钟传输一位数据。根据时钟极性和时钟相位的不同,将SPI分为了4种传输模式,详见表7.11。

表7.11 SPI模式

嵌入式

  • 时钟极性(CPOL)

时钟极性表示了SPI空闲时的时钟极性,可以为高电平(CPOL=1)或低电平(CPOL=0)。

  • 时钟相位

时钟相位决定了数据采样的时机,若CPHA=0,则表示数据在时钟的第一个边沿采样;若CPHA=1,则表示数据在时钟的第二个边沿采样。

CPHA=0时,对应的模式0(CPOL=0)和模式2(CPOL=1)的示意图详见图7.6。

嵌入式

图7.6 SPI模式0和模式2示意图

在SPI模式0(CPOL=0,CPHA=0)中,时钟空闲电平为低电平,在传输数据时,每一位数据在第一个边沿(即上升沿)采样。

在SPI模式2(CPOL=1,CPHA=0)中,时钟空闲电平为高电平,在传输数据时,每一位数据在第一个边沿(即下降沿)采样。

同理,CPHA=1时,对应的模式1(CPOL=0)和模式3(CPOL=1)的示意图详见图7.7。

嵌入式

图7.7 SPI模式1和模式3示意图

在SPI模式1(CPOL=0,CPHA=1)下,时钟空闲电平为低电平,在传输数据时,每一位数据在第二个边沿(即下降沿)采样。

在SPI模式3(CPOL=1,CPHA=1)下,时钟空闲电平为高电平,在传输数据时,每一位数据在第二个边沿(即上升沿)采样。

7.3.2  SPI总线接口

绝大部分情况下,MCU都作为SPI主机与SPI从机器件通信,因此这里仅介绍AWorks中将MCU作为SPI主机的相关接口,接口原型详见表7.12。

表7.12 SPI标准接口函数嵌入式

1.  定义SPI从机器件实例

对于用户来讲,使用SPI总线的目的往往是用于操作一个SPI从机器件,比如,HC595、SPI FLASH等。MCU作为SPI主机与从机器件通信,需要知道从机器件的相关信息,比如: SPI模式、SPI速率、数据位宽等。在AWorks中,定义了统一的SPI从机器件类型:aw_spi_device_t,用于包含从机器件相关的信息,以便主机正确的与之通信。该类型的具体定义用户无需关心,在使用SPI操作一个从机器件前,必须先使用该类型定义一个与从机器件对应的器件实例,例如:

嵌入式

其中,spi_dev为用户自定义的从机实例,其地址可作为接口函数中p_dev的实参传递。

2.  初始化SPI从机器件实例

当完成SPI从机器件实例的定义后,还需要完成其初始化,指定从机器件相关的信息,初始化函数的原型为:

嵌入式

其中,p_dev为指向SPI从机器件实例的指针,busid为SPI总线编号,bits_per_word为数据位宽,mode为使用的SPI模式,max_speed_hz为从设备支持的最高时钟频率,cs_pin为从机设备的片选引脚,pfunc_cs为自定义的片选引脚控制函数。

在AWorks中,一个系统往往具有多条SPI总线,各总线之间通过busid区分,一个系统实际支持的SPI总线条数与具体硬件平台相关。例如,在i.MX28x系统中,最高可以支持5条SPI总线,对应的总线编号即为:0 ~ 4。busid参数即用于指定使用那条SPI总线与从机器件进行通信。

表7.13 SPI模式标志嵌入式

bits_per_word指定了数据传输的位宽,一般来讲,数据均为8位,即每个数据为一个字节。

mode为从机使用的SPI模式(模式0 ~ 模式3,对应的宏定义详见表7.13),从机器件一般支持固定的一种或几种SPI模式,这里的mode用于指定使用何种SPI模式与从机器件通信。

max_speed_hz为从机器件支持的最大速率(SCLK最高时钟频率),往往在从机器件对应的数据手册上有定义。通常情况下,从机器件支持的最大速率都是很高的,有时可能会超过MCU中SPI主机能够输出的最大时钟频率,此时,将直接使用MCU中SPI主机能够输出的最大时钟频率。

cs_pin和pfunc_cs均与片选引脚相关。pfunc_cs是指向自定义片选引脚控制函数的指针,若pfunc_cs的值为NULL,驱动将自动控制由cs_pin指定的引脚实现片选控制;若pfunc_cs的值不为NULL,指向了有效的自定义片选控制函数,则cs_pin不再被使用,片选控制将完全由pfunc_cs指向的函数实现。当需要片选引脚有效时(SPI数据传输前),系统将自动调用pfunc_cs指向的函数,并传递state的值为1。当需要片选引脚无效时(SPI数据传输结束后),也会调用pfunc_cs指向的函数,并传递state的值为0。一般情况下,片选引脚自动控制即可,即设置pfunc_cs的值为NULL,cs_pin的值设置为片选引脚,比如:PIO2_19。

例如,要使用SPI0(SPI总线的busid为0)操作74HC595。则首先应该定义并初始化一个与74HC595对应的从机器件。这就还需要知道几点重要的信息:数据位宽、模式、最高速率和片选引脚。

要获取这些信息,就必须查看芯片相关的数据手册和相应的硬件电路。74HC595是一种常用的串行输入/并行输出转换芯片,其引脚分布图详见图7.8。

嵌入式

图7.8 74HC595管脚图

74HC595内部有两个8位寄存器:一个移位寄存器和一个数据锁存寄存器。移位寄存器的8位数据使用Q0’ ~ Q7’表示,其中仅Q7’位对应的电平通过Q7’引脚输出,其余位未使用引脚输出。数据锁存寄存器的8位数据使用Q0 ~ Q7表示,并使用Q0 ~ Q7引脚将8位数据输出。

移位寄存器在时钟信号CP的作用下,每个上升沿将D引脚电平移至移位寄存器的最低位,其余位依次向高位移动,移位寄存器的值发生改变,Q7’引脚的输出随着值的改变而改变,但此时,数据锁存寄存器中的值保持不变,即Q0 ~ Q7的输出保持不变。由此可见,由于移位作用,原移位寄存器中的最高位Q7’将被完全移除,数据丢失。若希望数据不丢失,即并行输出的数据超过8位,则可以将多个74HC595串接,将Q7’引脚连接至下一个74HC595的数据输入端D。这样,每次移位时,原先的Q7’将移动至下一个74HC595中,这也是为什么单独将Q7’通过引脚引出的原因,通过多个74HC595级联,可以将并行输出扩展为16位、24位、32位等。

数据锁存寄存器的值可以通过STR引脚输入上升沿信号更新,当STR引脚输入上升沿信号时,数据锁存寄存器中的值将更新为移位寄存器中的值,即Q0 ~ Q7的值更新为Q0’~Q7’的值。这样的设计可以保证同时改变所有并行输出,将8位数据一次性地在Q0 ~ Q7端输出,如果不使用数据锁存寄存器,而是直接将移位寄存器的值输出,则输出将受到移位过程的影响,即每次移位,数据输出都可能发生变化。

除基本的CP时钟信号、D数据输入信号、STR锁存信号、Q0 ~ Q7输出信号、Q7’输出信号外,还有嵌入式嵌入式两个控制信号,嵌入式为锁存寄存器的输出使能信号,当嵌入式嵌入式嵌入式嵌入式嵌入式嵌入式嵌入式

74HC595传输数据的过程为:D端为数据输入口,在时钟CP的作用下每次传输一位数据至移位寄存器中,如需传输8位数据,则应在CP端输入8个时钟信号,传输结束后,需要在STR上产生一个上升沿信号,以便将移位寄存器中的数据输出。由此可见,其数据传输的方式和SPI传输数据的方式极为相似,均是在时钟信号的同步下,每个时钟传输一位数据,特殊的,74HC595在传输结束后需要在STR上输入一个上升沿锁存信号。实际上,在SPI传输中,传输数据前,主机会将片选信号拉低,传输结束后,主机会将片选信号拉高,显然,若将STR作为从机片选信号由主机控制,则在数据传输结束后,主机会将片选信号拉高,同样可以达到在STR上产生上升沿的效果。

基于此,可以将74HC595看作一个SPI从机器件,SPI主机的SCLK时钟信号与CP相连,MOSI作为主出从入,与D相连,CS作为片选信号,与STR相连。此外,74HC595作为一个串/转并芯片,只能输出数据,不能输入数据,即SPI主机只需向74HC595发送数据,不需要从74HC595接收数据,因此,无需使用SPI的MISO引脚。

MiniPort-595模块采用74HC595扩展8路I/O,可以直接驱动LED显示模块,其等效电路详见图7.9。由此可见,模块仅有3路信号输入,但可并行输出8位数据。电路中,将 直接接地,固定为低电平,使能了数据锁存寄存器的输出。将 直接与电源连接,固定为高电平,即不使用其复位功能,使其不影响移位寄存器的值。

嵌入式

图7.9 74HC595电路图

实际中,当MiniPort-595与i.MX28x的扩展板连接时,MOSI、SCLK与i.MX28x的SPI0连接,片选信号CS与PIO2_19连接。

在初始化与74HC595对应的SPI从机器件实例时,还需要获取到数据宽度、SPI模式、最高时钟频率等信息。即:

  • 数据宽度

74HC595只有8个并行输出口,内部寄存器也均为8位,因此,单次传输的数据为8位,SPI传输时的数据宽度即为8。

  • SPI模式

数据在CP时钟信号的上升沿被采样送入74HC595的移位寄存器,对空闲时钟电平没有要求,根据SPI几种模式的定义可知(详见图7.6和图7.7),模式0和模式3均是在时钟上升沿采样数据,因此,使用模式0和模式3均可,后续的程序选择模式3作为范例。

  • 最高时钟频率

虽然74HC595支持的最高时钟频率可达100MHz,但实际中,受到MCU处理速度的限制以及MCU输出引脚翻转频率的限制,往往并不能达到该值。因此,最高时钟频率可以先设置在一个相对合理的范围,比如,3000000Hz(3MHz)。后续若3MHz不满足应用需求,可以在此基础上根据需要调大调小即可。

基于以上信息,可以定义并初始化一个与74HC595对应的SPI从机器件实例,范例详见程序清单7.20。

程序清单7.20 初始化从机器件实例的范例程序嵌入式

3.  设置SPI从机器件实例

设置SPI从机实例时,会检查MCU的SPI主机是否支持从机实例的相关参数和模式。如果不能支持,则设置失败,说明该从机不能使用。其函数原型为:

嵌入式

其中,p_dev是指向SPI从机器件实例的指针。返回值为标准的错误号,返回AW_OK时表示设置成功,否则,表示设置失败,存在不支持的位宽、速率或模式等。

例如,在完成74HC595的初始化后,再通过该接口设置一次74HC595器件实例,范例程序详见程序清单7.21。

程序清单7.21 设置SPI从机器件实例的范例程序嵌入式

4.  先写后读

当设定好从机实例后,即可与从机器件进行数据交互。虽然SPI协议是可以全双工通信的,即数据的发送和接收同时进行,但绝大部分情况下,并不会同时发送数据或接收数据,往往数据时单向进行的,即发送的时候不接收数据,或接收的时候不发送数据。

基于常见的SPI从机器件的操作方法,AWorks定义了两种情形下的数据通信:先写入一段数据至从机,再读取一段数据;先写入一段数据至从机,再写入一段数据。之所以均是先写入一段数据,是因为绝大多数数从机器件在操作前,首先都要发送一段命令数据至从机,以指定接下来的具体操作(读数据或写数据)。

先写后读即是主机先发送数据至从机(写),再自从机接收数据(读)。注意,该函数会等待数据传输完成后才会返回,因此该函数是阻塞式的,不应在中断环境中调用。其函数原型为:

嵌入式

其中,p_dev指向从机器件实例,表示本次数据通信的目标对象,p_txbuf指向需要首先写入从机的数据,n_tx为写入数据的字节数。p_rxbuf指向数据发送完成后,接收数据的缓冲区,n_rx为接收数据的个数。返回值为标准的错误号,返回AW_OK时表示数据传输成功,否则,表示数据传输失败。

注意,若只需要写入数据,不需要读取数据,则可以将p_rxbuf设置为NULL,n_rx设置为0,同理,若只需要读取数据,不需要发送数据,则可以将p_txbuf设置为NULL,n_tx设置为0。例如,需要发送数据至74HC595,以并行输出8位数据0x55,则范例程序详见程序清单7.22。

程序清单7.22 先写后读范例程序(1)嵌入式

为了更加直观的观察输出数据是否正确,可以将MiniPort-LED与MiniPort-595连接,等效电路图详见图7.10,由此可见,74HC595的输出端为低电平时,对应LED点亮, 0x55对应的二进制为01010101,因此,若成功输出,则可以观察到LED1、LED3、LED5和LED7被点亮,LED0、LED2、LED4和LED6被熄灭。

嵌入式

图7.10  MiniPort-595 + MiniPort-LED等效电路图

由于74HC595使用简单,只能写入数据,因此,上述范例程序并没有用到读取数据。实际上,对于常见的SPI从机器件,其通信协议往往都是采用“命令”+“数据”的格式。如典型的SPI FLASH从机器件:MX25L1606,在对其操作时,均需要先写1字节的命令,后续再是具体的数据,具体数据的含义随着命令的不同而不同。例如,每个MX25L1606内部具有一个3字节的电子ID号(RDID),如需读取该ID,则需要先发送单字节的命令0x9f。则使用先写后读接口读取ID的关键语句详见程序清单7.23。

程序清单7.23 先写后读范例程序(2)嵌入式

更多命令以及RDID的含义详见相应的数据手册,这里仅用于说明对于这种常见的操作方法,如何正确的使用SPI接口。

5.  连续两次写

连续两次写,即主机先发送一段数据至从机(写),发送结束后,再发送一段数据至从机(写)。注意,该函数会等待数据传输完成后才会返回,因此该函数是阻塞式的,不应在中断环境中调用。其函数原型为:

嵌入式

其中,p_dev指向从机器件实例,表示本次数据通信的目标对象,p_txbuf0指向需要首先写入从机的数据,n_tx0为写入数据的字节数。p_txbuf0指向数据发送完成后,开始发送下一段数据,p_txbuf1指向需要再次写入从机的数据,n_tx1为写入数据的字节数。返回值为标准的错误号,返回AW_OK时表示数据传输成功,否则,表示数据传输失败。

注意,若只需要写入一段数据,则可以将p_txbuf1设置为NULL,n_tx1设置为0。

同样可以使用该接口发送数据至74HC595,例如,使用MiniPort-595控制MiniPort-LED,使LED0 ~ LED7依次循环点亮,实现简易的流水灯效果,范例程序详见程序清单7.24。

程序清单7.24 执行连续两次写的范例程序(1)嵌入式

程序中,由于LED是低电平点亮,因此,通过74HC595输出数据时,需要将data取反,以便使数据中与需要点亮的LED相应位的值为0。

由于74HC595只能写入单字节数据,因此,上述范例程序并没有使用到第二次的数据写入,第二次写入的数据缓冲区被设定为了NULL。为了更全面的说明该接口的用法,同样以MX25L1606为例,如果要在SPI FLASH存储器中存入一段数据,则应先发送命令0x02和存储数据的起始地址(3字节,24位),接着再发送实际的待存储的数据。写入数据至指定地址的关键语句详见程序清单7.25。

程序清单7.25 执行连续两次写的范例程序(2)嵌入式

实际中,写入数据前,应确保相应区域被擦除,擦除同样有相关的命令,更多命令以及数据存储的注意事项详见相应的数据手册,这里仅用于说明连续两次写接口的使用方法。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分