This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] MSP432P401R:捕获SPI字节传输的结束

Guru**** 2587345 points


请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/617965/msp432p401r-catching-the-end-of-spi-byte-transmission

部件号:MSP432P401R

您好!

使用MSP432微控制器设置SPI传输时遇到一些问题。
我是这款控制器的新手,所以这个问题可能很明显。

基本上,我正在尝试驱动一个带有SPI接口的EA DOGL-128图形LCD显示器。

SPI接口的初始化方式如下:

#include <driverlib.h>
#include <SPI_drv.h>/*

SPI主配置参数*/
const eUSI_SPI_MasterConfig spiMasterConfig ={

EUSCI_B_SPI_CLOCKSOURCE_SMCLK, // SMCLK时钟源
1200万, // SMCLK = DCO = 12MHz
1万, // SPICLK = 10kHz
EUSCI_B_SPI_MSB_FIRST, // MSB优先
EUSCI_B_SPI_PHASE DATA_Changed_ONFIRST_Captured_on_NEXT,//相位
EUSCI_B_SPI_CLOCKPOLARITY_INactivity HIGH,//高极性
EUSCI_B_SPI_3引脚 // 3Wire SPI模式
};


Public void SPI_init(void)
{
/*在SPI模式下选择WFP 1.5 和WFP 1.6 */
GPIO _setAsPeripheralModuleFunctionInputPin (GPIO_PORT_P1,
GPIO _PIN5 | GPIO _PIN6,GPIO主要模块功能);

//将引脚4.3 设置为CS引脚。
GPIO _setAsOutputPin (GPIO端口P4,GPIO _PIN3);

/*我们应该给CS引脚提供下降边缘,但不完全确定是否有必要这样做。 */
/*可能我们必须捕获传输中断,然后控制CS??? 尚不清楚这是如何工作的。 */
GPIO _setOutputHighOnPin (GPIO端口P4,GPIO _PIN3);

/*在3Wire主模式下配置SPI */
SPI_initMaster(EUSI_B0_BASE,&spiMasterConfig);

/*启用SPI模块*/
SPI_enableModule(EUSI_B0_BASE;
} 

这就是传输函数的外观:

公共空SPI_Transmit_Byte (U8字节)
{
/*轮询以查看TX缓冲区是否就绪*/
同时(!(SPI_getInterruptStatus (EUSCI_B0_BBASE,EUSCI_B_SPI_Transmit_Interrupt)));
/*将数据传输到从属设备*/
SPI_SPI_SpenDate(EUSI_B0_Base,byte);
} 

现在SPI传输本身工作正常,使用逻辑分析器进行检查,似乎没有问题。
问题是液晶屏上有一个A0针脚,该针脚主要确定是否应该解释我发送的命令  
作为写入数据或作为命令。 需要在SPI字节传输结束时将A0引脚设置为低/高。

我一开始就尝试这样控制它:

私有void disp_command(U8 cmd,布尔注册选择){

IF (REG_SELECT)
{
GPIO _setOutputHighOnPin (GPIO_PORT_P1, GPIO _PIN7);
}
否则
{
GPIO _setOutputLowOnPin (GPIO_PORT_P1, GPIO _PIN7);
}
SPI_Transmit_Byte (cmd);
} 

但这不起作用,因为A0针脚在前一字节完成发送之前就已设置好。

所以我尝试在SPI_Transmit_Byte函数中设置A0。  

公共空SPI_Transmit_Byte (U8字节,布尔REG_SELECT)
{
/*轮询以查看TX缓冲区是否就绪*/
同时(!(SPI_getInterruptStatus (EUSCI_B0_BBASE,EUSCI_B_SPI_Transmit_Interrupt)));
/*将数据传输到从属*/
	IF (REG_SELECT)
{
GPIO _setOutputHighOnPin (GPIO_PORT_P1, GPIO _PIN7);
}
否则
{
GPIO _setOutputLowOnPin (GPIO_PORT_P1, GPIO _PIN7);
}
	
SPI_SPI_SpenDate(EUSI_B0_Base,byte);
} 

但是,在这种情况下,A0也设置在错误的时间。 更确切地说,它是在上一个过程中设置的
字节,因此传输不正确。 基本上我理解它的方式是,EUSCI_B_SPI_Transmit_Interrupt被激发

当SPI接口已准备好用于新字节,但旧字节尚未完成发送时。 我需要做的是抓住时机
当MCU开始实际发送一个字节,然后控制A0引脚时。 现在我看不到一个好的方法。也许有人遇到过类似的问题。

PS! 在代码中有以下定义:

#定义公共
#定义私有静态

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    只是一个想法:

    也许我可以为EUSCI_B_SPI_Transmit_interrupt创建一个中断处理程序。

    调用SPI_Transmit_Byte时,我们将所需的A0引脚状态存储在全局变量中。 当EUSCI_B_SPI_Transmit_Interrupt时
    然后,我们知道接口已准备好接收新的TX数据,因此它应该正在发送上一个字节,因此我们将读取
    全局变量,并基于该变量驱动中断处理程序中的A0引脚。

    不过,对我来说,这似乎是一个粗略的解决方案
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    您可以在发送最后一个字节后轮询STATW:UCBUSY,以了解SPI何时进入空闲状态。

    在MSP432芯片的早期版本中,UCBUSY会出现问题(阅读:“黑色Launchpad”)。 如果你有一个"红色启动板",你应该没问题。
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    不幸的是,我们使用了一个黑色的洗涤垫:(-仍然会尝试一下,让你知道它是如何进行的。
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您是否使用DMA控制器? 显示屏接收大量数据,因此DMA在此处可能很方便。 不管怎样-如果您不使用DMA并且您可以承受字节数之间的小间隙,您也可以在RX中断而不是TX中断上触发。 在这种情况下,您肯定知道字节何时被完全移出。

    Dennis

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    嗯...液晶屏不会再传输任何内容,因此它仅传输。 但我想这并不重要,因为我可以
    每次只接收0x00并丢弃数据。 感谢您的想法,我也会尝试一下。
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    液晶屏不必传输任何数据。  根本不需要读取数据,只需清除RX中断标志-您甚至无需配置miso引脚即可触发RX中断。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    Dennis认为DMA对于图形显示(通常)所需的批量Tx非常有用。 不幸的是,它不会使您脱离UCBUSY业务,因为DMA只是写入(缓冲的) TXBUF,所以DMA在SPI完成之前就完成了。

    你也可以在Rx端做DMA (到一个比特桶),这会使事情保持同步,但有点过度。

    当我用黑色的Launchpad做这件事的时候,我最后用(“不要在家里尝试,孩子”)计数钟。 SPI的其中一个乐趣是它的时间是确定性的-- 根据MCLK/SMCLK比率和USCI时钟分配器,您知道在DMA完成后,事务必须在一定的时钟数内完成,所以我只是使用__DELAY_CYCLES等待,直到我知道它一定已经完成。 它有点笨拙,但它只应用于最后(也许几百个)字节。
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    是的,此时我只是尝试让液晶屏启动并显示"任何内容",所以我认为DMA有点过度。 我可能只是尝试用非常愚蠢的代码来运行它,比如在每个命令之前都进行50毫秒的延迟,这样我就知道PIN是正确的配置。 当我知道至少某件事在起作用时,我可以在以后尝试更高级的解决方案:)
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    Dennis对图形显示(通常)所需的批量Tx来说,DMA非常有用。 不幸的是,它不会让您脱离UCBUSY业务,因为DMA只是写入(缓冲的) TXBUF,因此DMA在SPI完成之前就完成。

    完全同意这一点。 我很久以前就看到自己在同样的情况下使用图形显示器。 我使用了EA DOGS102部件。 我最后使用了DMA的组合,用于在DMA信号完成后发送数据和轮询BUSY标志,以识别USCI模块的已完成传输。 还有黑色的LaunchPad -我从未遇到过UCBUSY的任何问题。

    Dennis

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    Joonatan Renel 说:
    是的,此时我只是尝试启动液晶屏并显示"任何内容"[/QUOT]

    然后在RX中断上触发的方法是100 % 安全, 虽然这种方法不是最好的方法,因为字节之间有小的间隙(这对LCD来说不是问题),而且每个字节之后都有一个中断(但触发TX中断时也是这种情况)。

    Dennis

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    在侧注-我没有时间研究A0针脚解决方案,但我确实了解了为什么显示屏不做任何操作(例如,甚至接受显示所有点等命令, 不使用A0)-我将 ˇRESET Ω 线插入MSP432上的 ˇRST Ω 引脚,只是尝试一下。 在我使用通用IO引脚之前,我认为可能在开始时将其设置为低电平是错误的方法。 DOGL 128可能需要执行完整的重置循环,这意味着它必须达到高值,然后在那里停留一小段时间,然后再次降低。

    无论如何,显示屏现在处于活动状态并显示垃圾(在处理A0针问题之前,这是预料之中的):)
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    Joonatan Renel 说:
    不管怎样,显示屏现在处于活动状态并显示垃圾(在处理A0引脚问题之前,这是预期的)[/QUOT]

    您可以尝试RX中断解决方案-然后您始终知道每个字节的传输状态,并且您可以在必要时设置或清除A0引脚。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    Dennis说的。 在伪代码-ish中:

    while (!TXIFG)/*空*/;
    TXBUF =字节;
    同时(!RXIFG)/*空*/;
    (void) RXBUF;//清除RXIFG

    由于流水线中不会有多个字节,RXIFG会告诉您该字节已发送(您可以更改A0)。
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    >还有黑色的LaunchPad -我从未遇到过UCBUSY的任何问题。

    这并不是一直都发生的。 但这只能发生一次。
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    但我会使用中断而不是轮询RX标志。 这些字节数相当大,控制器将在很长的时间内保持循环。
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    好的,看起来我的A0引脚正常工作,解决方案不理想,因为它在发送的字节中产生了间隙,但它是一个很好的起点。 我想如果我花些时间来研究这个问题,我可能会找到更好的解决方案,但测试这种解决方案已经足够好了。

    代码如下:

    /*
    * SPI.c
    *
    *创建日期:2017年8月13日
    * 作者:Joonatan
    */
    
    #include <driverlib.h>
    #include <SPI_drv.h>
    #include "register.h"
    
    /* SPI主配置参数*/
    const eUSI_SPI_MasterConfig spasterConfig ={
    
    EUSCI_B_SPI_CLOCKSOURCE_SMCLK, // SMCLK时钟源
    1200万, // SMCLK = DCO = 12MHz
    10万, // SPICLK = 100kHz
    EUSCI_B_SPI_MSB_FIRST, // MSB优先
    EUSCI_B_SPI_PHASE DATA_Changed_ONFIRST_Captured_on_NEXT,//相位
    EUSCI_B_SPI_CLOCKPOLARITY_INactivity HIGH,//高极性
    EUSCI_B_SPI_3引脚 // 3Wire SPI模式
    };
    
    
    Public void SPI_init(void)
    {
    /*在SPI模式下选择WFP 1.5 和WFP 1.6 */
    GPIO _setAsPeripheralModuleFunctionInputPin (GPIO_PORT_P1,
    GPIO _PIN5 | GPIO _PIN6,GPIO主要模块功能);
    
    /*在3Wire主模式下配置SPI */
    SPI_initMaster(EUSI_B0_BASE,&spiMasterConfig);
    
    /*启用SPI模块*/
    SPI_enableModule(EUSI_B0_BASE;
    
    /*启用中断*/
    SPI_enableInterrupt (EUSCI_B0_BBASE,EUSCI_B_SPI_receive _interrupt);
    interrup_enableInterrupt (INT_EUSCIB0);
    }
    
    公用空间SPI_Transmit (U8 * data,U16 DATA_len)
    {
    U8 * DATA_PTR =数据;
    
    while (data_len > 0)
    {
    SPI_Transmit_Byte (*DATA_PTR,FALSE);
    DATA_PTR++;
    data_len--;
    }
    }
    
    易失性布尔值isReadyToTransmit = TRUE;
    
    
    //******************************************************************************
    //
    //这是EUSCI_B0中断向量服务例程。
    ////************************************************************************************************
    
    void EUSCIB0_IRQHandler (void)
    {
    UINT32_t status = SPI_getEnabledInterruptStatus(EUSSCI_B0_BASE;
    
    SPI_clearInterruptFlag (EUSCI_B0_BASE,STATUS);
    
    IF (状态和EUSCI_B_SPI_Receive_interrupt)
    {
    isReadyToTransmit =真;
    }
    }
    
    公用空间SPI_Transmit_Byte (U8字节,布尔REG_SELECT)
    {
    //将A0引脚设置为正确状态。
    while (!isReadyToTransmit);
    
    IF (REG_SELECT)
    {
    set_reg_select(1U);
    }
    否则
    {
    set_reg_select(0U);
    }
    
    isReadyToTransmit =假;
    //将数据传输到从属设备。
    SPI_SPI_SpenDate(EUSI_B0_Base,byte);
    }
    

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    仅供参考,这是一个将命令写入显示器的文件,也许有人对未来感兴趣。
    我在其他地方为销钉控制创建了函数,但这不应该太困难。

    该代码基本上初始化EA DOGL128 LCD显示屏,将其清除,然后将一些行写入其中一个显示屏段。
    它需要大量的工作,才能转化为任何有用的东西,但我认为这是一个良好的起点。

    /*
    * display_drv.c
    *
    *创建时间:2017年8月13日
    * 作者:Joonatan
    */
    
    #include "display_drv.h"
    #include <driverlib.h>
    #include "SPI_drv.h"
    #include "register.h"
    
    #define number_of_pages 8u
    #define number_of_columns 128u
    
    /* todo:其中一些可以用宏或内联函数替换。 */
    
    private void disp_command(U8 cmd, Boolean reg_select);
    private void set_page_address(U8 page);
    private void set_column(U8 column);
    private void cleal_display(void);
    private void
    display_reset(void);
    
    private u8 display_buffer[number_of_columns][pin_set_up]
    
    
    */
    Public void display_init(void)
    {
    //最初将芯片选择引脚设置为高。
    memset (display_buffer,0x00u,sizeof (display_buffer));
    DISPLAY_RESET();
    set_cs_pin(1U);
    }
    
    公共空间DISPLAY_TEST_SEQUENCE (void){
    
    U8 ix;
    
    //低CS,表示它处于活动状态。
    set_cs_pin (0U);
    
    //显示起点线设置。
    disp_command(0x40u, false);
    
    //将ADC设置为反向
    disp_command(0xA1u, false);
    //常用输出模式选择
    disp_command(0xC0u, false);
    
    //正常显示
    disp_command(0xA6u, false);
    
    //LCD偏置设置
    disp_command(0xA2u, false);
    
    //电源控制设置
    disp_command(0x2Fu,false);
    
    //已设置增压器比率。
    disp_command(0xF8u, false);
    disp_command(0x00u, false);
    
    //对比度设置。
    disp_command(0x27u, false);
    disp_command(0x81u, false);
    disp_command(0x10u, false);
    
    //静态指示器设置。
    disp_command(0xACu, false);
    
    //打开显示屏。
    disp_command(0x00u, false);
    disp_command(0xAFu, false);
    
    delay_msec (200);
    //打开所有点。
    disp_command((0xA4u | 0x01u),false);
    cleal_display();
    delay_msec (2000);
    //关闭所有点。
    disp_command(0xA4u, false);
    
    //让我们尝试编写一些内容。
    
    //设置为寻呼地址2.
    set_page_address (4U);
    set_column (0U);
    
    对于(ix = 0U;ix < 128u;ix++)
    {
    //写入数据。
    //disp_command(0xAau,true);
    write_data(0xAau);
    }
    }
    
    
    私有void disp_command(U8 cmd,布尔注册选择){
    
    spI_transmit_byte(cmd, reg_select);
    }
    
    
    私有void set_page_address(U8 page){
    
    disp_command(0xB0u |(page & 0x0fu),false);
    }
    
    
    私有void write_data(U8 data)
    {
    disp_command(data, true);
    }
    
    
    私有void set_column(U8 column){
    
    //设置列MSB。
    disp_command(0x10u |(列>> 4U),FALSE);
    //设置列LSB。
    disp_command(column & 0x0fu,false);
    }
    
    
    私有void clean_display (void)
    {
    U8 ix;
    U8 yx;
    
    对于(ix = 0U;ix < number_of_pages;ix++)
    {
    set_page_address (ix);
    set_column(0);
    对于(yx = 0U;yx < number_of_columns;yx++)
    {
    write_data(0x00u);
    }
    }
    
    
    
    私有void display_reset(void){
    
    set_disp_reset_pin (0U);
    delay_msec (100);
    set_disp_reset_pin (1u);
    delay_msec(100);
    } 

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    知道它通常是有效的通常是最重要的事情-从这一点开始,优化就可以开始;)听到它的工作很好!
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。
    /*
    * typedefs.h
    *
    *创建日期:21.02 2016
    * 作者:Joonatan
    */
    
    #ifndef typedefs_H_
    #define typedefs_H_
    
    #include <string.h>
    #include <stdio.h>
    
    typedef unsigned char U8;
    typedef unsigned short U16;
    typef unsigned pedint U32;
    
    typedef { false,true } Boolean;
    
    //////////// 掩码
    #define ISBIT(p,b)(p & b)
    #define setit(p,b) p |= b
    #define CLRBIT(p,b) p &=~b
    
    #define bit_0 0x01u
    #define bit_1 0x02u
    #define bit_2 0x04u
    #define bit_3 0x08u
    #define bit_4 0x10u #def8
    
    
    
    
    bit_0 x80u
    #def5 h_定义static #def6 #def6 b_20位/0x80u #def5 #def6 #def8位/0x80u_定义static #def6位#x7#定义static #xf_位/0x80u_
    
    

    还添加了一些类型定义->可能会使代码更容易理解。