前面都只是根据TI MSPWare例程讲,感觉比较无聊,最后这两个结合实际使用来讲,贴出的代码在MSP430G2 LAUNCHPAD和对应芯片上测试通过。这一次是硬件SPI模块。一般像这种同步串口(不知道什么是同步串口和异步串口的请自行google)多数情况用GPIO模拟是最简单的,但随着对速度要求的提高,再用GPIO软件模拟的话就很难做到了,比如在STM32上实测用GPIO模拟SPI协议,速度最高也就不到2M,而硬件最高能达到36M,这时候就需要使用硬件SPI。硬件SPI另一个好处是数据收发由专用硬件模块实现,收发时候不占用CPU,如果结合DMA(直接内存访问)使用,在整个传输过程中都不需要CPU参与,复杂任务下可以大大节约CPU资源。不像IIC,SPI是一个约定俗成的协议,不需要专利费,所以也就没有各种隐藏bug(此段为段子,如有雷同纯属巧合)。但是也因为SPI没有确定的协议,因此有好几种工作模式,需要根据具体器件设置。实例中根据SPI FLASH设置SPI工作模式,如果用到别的器件上可能需要根据实际情况修改配置参数。
首先上一张SPI时序图,所有操作都以时序图为参考。图片来源是MSP430G2系列用户手册。
可见四种SPI模式是数据采样时间不同,为方便说明,UCxCLK的四种情况从上到下依次编号为1,2,3,4。对于数据移位和采样时间,在UCxCLK是情况1和4时,数据引脚电平(MOSI,MISO)在时钟的上升沿改变,在下降沿被采样,情况2和3是下降沿改变,上升沿采样。对于时钟电平空闲时的状态,在UCxCLK是情况1和3时是空闲状态时钟为低,情况2和4是空闲状态时钟为高。
对于FLASH支持的SPI模式可在FLASH手册中找到,多数FLASH都支持UCxCLK情况2和3,即数据引脚电平在下降沿改变,在上升沿采样,而对于时钟引脚空闲电平不敏感。
因此,程序中配置SPI为时钟空闲为高,上升沿采样,下降沿数据改变。SPI初始化代码如下:
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
P1OUT = 0x00;
P1DIR |= BIT0 + BIT3 + BIT5;
P1OUT |= BIT0 + BIT3 + BIT5; // Control pins assignment
P1SEL = BIT1 + BIT2 + BIT4;
P1SEL2 = BIT1 + BIT2 + BIT4; // SPI pins assignment
UCA0CTL0 |= UCCKPL + UCMSB + UCMST + UCSYNC; // 3-pin, 8-bit SPI master
UCA0CTL1 |= UCSSEL_2; // SPI Clock source is SMCLK
UCA0BR0 |= 0x02; // SPI clock = SMCLK / 2
UCA0BR1 = 0; //
UCA0MCTL = 0; // No modulation
UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
IFG2 &= ~(UCA0TXIFG + UCA0RXIFG); // Clear TX and RX interrupt flag
P1OUT &= ~BIT5; // Now with SPI signals initialized, select slave device
IE2 |= UCA0TXIE; // Enable TX interrupt
__bis_SR_register(GIE); // Enable global interrupts
在MSP430G2553手册里对于SPI引脚连接有说明。因为用到了SPI模块,所以只有特定引脚可以连接设备。
如图中,P1.1为SPI SOMI引脚,P1.2为SPI SIMO引脚,P1.4为SPI CLK引脚,这三个引脚要接到SPI FLASH的对应引脚上,SPI的CS引脚在有些芯片中提供硬件控制功能,有些没有。因为一个SPI总线上可以挂多个器件,通过CS引脚选择要进行操作的器件,在对器件进行操作之前需要先根据说明将CS引脚置于相应电平(一般是低电平表示选中对应器件),器件才会响应。不能同时有两个或以上器件被选中,否则数据读写会出错。在没有硬件控制CS引脚功能的芯片上,可以用GPIO控制。由于CS引脚在整个读写过程中可以一直保持有效,所以对读写速度影响不大。
图中FLASH芯片通过转接板连接到LAUNCHPAD,左边的连接线连接到别的模块上,程序中没有用到。在运行程序之前,先使用编程器读出FLASH内容如下:
然后运行程序,并同时用逻辑分析仪采集CLK,MOSI,MISO引脚的信号。程序图片右边能够读出FLASH内容,并且和编程器读出的内容一致,逻辑分析仪也能读出SPI传输的内容。
附上完整的读FLASH的程序。写FLASH的程序在此上面稍加修改即可。FLASH读写指令可以在芯片手册中查到。
#include <msp430.h>
#include <stdint.h>
#define SPI_CS BIT5
#define SPI_WP BIT3
#define SPI_HD BIT0
uint8_t SPI_Char[128];
uint8_t SPI_CMD[4] = {0x03, 0x00, 0x00, 0x00};
int main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
P1OUT = 0x00;
P1DIR |= BIT0 + BIT3 + BIT5;
P1OUT |= BIT0 + BIT3 + BIT5; // Control pins assignment
P1SEL = BIT1 + BIT2 + BIT4;
P1SEL2 = BIT1 + BIT2 + BIT4; // SPI pins assignment
UCA0CTL0 |= UCCKPL + UCMSB + UCMST + UCSYNC; // 3-pin, 8-bit SPI master
UCA0CTL1 |= UCSSEL_2; // SPI Clock source is SMCLK
UCA0BR0 |= 0x02; // SPI clock = SMCLK / 2
UCA0BR1 = 0; //
UCA0MCTL = 0; // No modulation
UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
IFG2 &= ~(UCA0TXIFG + UCA0RXIFG); // Clear TX and RX interrupt flag
P1OUT &= ~BIT5; // Now with SPI signals initialized, select slave device
IE2 |= UCA0TXIE; // Enable TX interrupt
__bis_SR_register(GIE); // Enable global interrupts
UCA0TXBUF = SPI_CMD[0]; // Send 1st byte
while(1);
}
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCIA0RX_ISR(void)
{
static uint8_t i = 0;
if(IFG2 & UCA0TXIFG)
{
if(i < 128)
SPI_Char[i++] = UCA0RXBUF; // Receive bytes
}
}
#pragma vector=USCIAB0TX_VECTOR
__interrupt void USCIA0TX_ISR(void)
{
static uint8_t i = 1;
if(IFG2 & UCA0TXIFG)
{
if(i < 4)
UCA0TXBUF = SPI_CMD[i++]; // Transmit control bytes
else
{
UCA0TXBUF = 0xFF;
i++; // Dummy tx bytes, for continuity of spi clock
}
if(i == 5)
IE2 |= UCA0RXIE; // Transmit end, enable receive interrupt
if(i >= 133)
{
P1OUT |= BIT5; // Deselect SPI slave device
IE2 &= ~UCA0RXIE; // Clear TX and RX interrupt
IE2 &= ~UCA0TXIE;
}
}
}