打印

请教下STM32F0+DMA+SPI驱动5110的问题

[复制链接]
5972|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
znsword|  楼主 | 2012-8-20 15:24 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
篇幅所限,这里只列部分代码,用得是STM32F0 DISCOVERY小板
初始化:
  /* RCC configuration */
  RCC_Config(); // 这里用的默认设置,没有外接晶振
  /* GPIO configuration */
  GPIO_Config();
  /* SPI configuration */
  SPI_Config();
  /* DMA configuration */
  DMA_Config();
  /* NVIC configuration */
  NVIC_Config();  
  /* Lcd initialize */
  LCD_Init();
  /* Lcd clear */
  LCD_Clear();

各部分初始化:
void RCC_Config(void)
{
  RCC_ClocksTypeDef RCC_Clocks;
  
  /* SysTick end of count event each 1ms */
  RCC_GetClocksFreq(&RCC_Clocks);
  SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);

  /* Enable APB1 bus clock */
  RCC_APB1PeriphClockCmd(SPI2_CLK, ENABLE);
  /* Enable AHB bus clock */
  RCC_AHBPeriphClockCmd(DMA1_CLK |
                        SPI2_GPIO_CLK |
                        LCD_GPIO_CLK, ENABLE);  
//  /* Enable SPI2 peripheral GPIO port clocks */
//  RCC_AHBPeriphClockCmd(SPI2_GPIO_CLK, ENABLE);
//  /* Enable nokia5110 control GPIO port clocks */
//  RCC_AHBPeriphClockCmd(LCD_GPIO_CLK, ENABLE);
}

/**
  * @brief  GPIO configuration.
  * @param  None
  * @retval None
  */
void GPIO_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
  
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  
  /* Connect GPIO pins for SPI2 as alternate function */
  GPIO_PinAFConfig(SPI2_GPIO_PORT, SPI2_SCK_SOURCE, SPI2_SCK_AF);
  GPIO_PinAFConfig(SPI2_GPIO_PORT, SPI2_MOSI_SOURCE, SPI2_MOSI_AF);
  
  /* SPI SCK and MOSI pin configuration */
  GPIO_InitStructure.GPIO_Pin = SPI2_SCK_PIN | SPI2_MOSI_PIN;  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  
  GPIO_Init(SPI2_GPIO_PORT, &GPIO_InitStructure);
  
  /* Nokia5110 control pin configuration */
  GPIO_InitStructure.GPIO_Pin = LCD_GPIO_DC |
                                LCD_GPIO_CE |
                                LCD_GPIO_RST |
                                LCD_GPIO_BL;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  
  GPIO_Init(LCD_GPIO_PORT, &GPIO_InitStructure);  
}

/**
  * @brief  SPI configuration. See the notes in detail
  * @param  None
  * @retval None
  */
void SPI_Config(void)
{
  SPI_InitTypeDef SPI_InitStructure;
  
  // Set up the SPI peripheral
  SPI_I2S_DeInit(SPI2);
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  SPI_InitStructure.SPI_CRCPolynomial = 7;
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
  SPI_Init(SPI2, &SPI_InitStructure);
//  SPI_NSSInternalSoftwareConfig(SPI2, SPI_NSSInternalSoft_Set);
  // Enable the SPI port
  SPI_Cmd(SPI2, ENABLE);
}

/**
  * @brief  DMA configuration. See the notes in detail
  * @param  None
  * @retval None
  */
void DMA_Config(void)
{
  DMA_InitTypeDef DMA_InitStructure;
  
  // Reset DMA configuration
  DMA_StructInit(&DMA_InitStructure);
  
  // 配置DMA控制器
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)lcd_buffer;
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI2_DR_ADDRESS);
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_BufferSize = 0;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(SPI_PORT_TX_DMA_STREAM, &DMA_InitStructure);

  // 使能DMA数据传输完成中断
  DMA_ITConfig(SPI_PORT_TX_DMA_STREAM, DMA_IT_TC, ENABLE);
  
  // CR2:TXDMAEN Tx缓冲DMA 使能。当此位被设置,TXE标志被设置时,产生一个DMA请求。
  // 使能SPI DMA发送请求。
  // 0: Tx 缓冲DMA禁止
  // 1: Tx 缓冲DMA使能
  SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);
//  DMA_Cmd(SPI_PORT_TX_DMA_STREAM, ENABLE);
}


/**
  * @brief  NVIC configuration.
  * @param  None
  * @retval None
  */
void NVIC_Config(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_InitStructure.NVIC_IRQChannel = SPI_PORT_DMA_TX_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);  
}

// 向5110中写入一个字符串
void LCD_WriteString(uint8_t X, uint8_t Y, uint8_t *s)
{
//  DMA_InitTypeDef DMA_InitStructure;
//  uint8_t len = 0;
  uint8_t *str = s;
  int i = 0;
  int lcd_index =0;
  
  if(str == 0 )
  {
    return ;
  }
//  // Wait for the previous DMS request to end
//  while(DMA_GetCurrDataCounter(SPI_PORT_TX_DMA_STREAM));
  
  LCD_Set_XY(X,Y);
  
  while(*str)
  {
    // Copy the display string to display buffer
    for(i=0; i<6; i++)
    {
      lcd_buffer[lcd_index++] = font6x8[*str-32][i];
    }
    str++ ;
  }
  lcd_buffer[lcd_index++] = 0 ; // lcd_index ++ 多发送一个0否者最后一个字符会缺少一个像素
  
  
//#if 1

//  Delay(1);
//  DMA_InitStructure.DMA_BufferSize = lcd_index;
  LCD_DC_DATA;  
  DMA_Cmd(SPI_PORT_TX_DMA_STREAM, DISABLE);
  SPI_PORT_TX_DMA_STREAM->CNDTR = lcd_index;
//  DMA_Init(SPI_PORT_TX_DMA_STREAM, &DMA_InitStructure);
  DMA_Cmd(SPI_PORT_TX_DMA_STREAM, ENABLE);
  
//  LCD_DC_DATA;  
//  SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);
  // Wait for the previous DMS request to end
//  while(DMA_GetCurrDataCounter(SPI_PORT_TX_DMA_STREAM));   
//  SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);
//  
//#else
  
  //LCD_DC_DATA();
  
//  for(i=0 ;i<lcd_index ;i++)
//  {
//    LCD_Send(lcd_buffer[i], LCD_DATA);
//  }
  
//#endif
}

中断服务程序
void SPI_PORT_DMA_TX_IRQHandler()
{
  // DMA_ISR:TCIF5 传输完成标志
  // 该位由硬件设置。由软件对DMA_IFCR 寄存器的相应位写1清除。
  // 0: 在通道x上无传输完成 (TC) 的事件发生
  // 1: 在通道x上发生了传输 (TC) 的事件
  if (DMA_GetITStatus(DMA1_IT_TC5))
  {
    // DMA_IFCR:CTCIF5 传输完成标志清除,该位由软件置1清除。
    // 0: 无效
    // 1: 在DMA_ISR寄存器中清除相应的TCIF标志
    DMA_ClearITPendingBit(DMA1_IT_TC5);
//    // SPI_SR:TXE 发送缓冲区为空标志
//    // 0: Tx 缓冲区非空
//    // 1: TX 缓冲器空
//    // 如果SPI2发送缓冲区非空,则在这里等待(中断服务函数里面做循环等待)!!!!
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) {
    };
//    // SPI_SR:BSY 忙标志
//    // 0: SPI(或I2S)不忙
//    // 1: (SPI 或I2S)通信忙或发送缓冲区不为空
//    // 如果通信忙,则在这里等待(中断服务函数里面做循环等待)!!!!
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) == SET) {
    };
  }
//  GPIO_SetBits(GPIOC, nokiaSce); //SCE pin high
//  STM_EVAL_LEDOff(LED4);
//  // 关闭SPI DMA传输
    DMA_Cmd(SPI_PORT_TX_DMA_STREAM, DISABLE);
}
中断可以进入,但是5110上什么也显示不出来。如果不用DMA,只用SPI,是可以显示的。请高手指导下。
沙发
IJK| | 2012-8-20 15:59 | 只看该作者
建议LZ用示波器来看,比较成功和失败的波形、数据,这样比较容易找出问题在哪里。

使用特权

评论回复
板凳
znsword|  楼主 | 2012-8-20 17:17 | 只看该作者
波形是不一致。但是数据怎么比较呢?发现一个问题:
  DMA_Cmd(SPI_PORT_TX_DMA_STREAM, DISABLE);
  SPI_PORT_TX_DMA_STREAM->CNDTR = lcd_index;
//  DMA_Init(SPI_PORT_TX_DMA_STREAM, &DMA_InitStructure);
  DMA_Cmd(SPI_PORT_TX_DMA_STREAM, ENABLE);
ENABLE 之后CNDTR立刻清零了。不明白这是怎么回事?

使用特权

评论回复
地板
IJK| | 2012-8-20 17:27 | 只看该作者
波形是不一致。但是数据怎么比较呢?发现一个问题:
  DMA_Cmd(SPI_PORT_TX_DMA_STREAM, DISABLE);
  SPI_PORT_TX_DMA_STREAM->CNDTR = lcd_index;
//  DMA_Init(SPI_PORT_TX_DMA_STREAM, &DMA_InitStructure);
  DM ...
znsword 发表于 2012-8-20 17:17


波形是不一致,是肯定的。其实正确要发的数据,LZ应该知道,就看用DMA后发送的数据是什么,1个、1个字节比较和判断,就容易了。

使用特权

评论回复
5
znsword|  楼主 | 2012-8-20 18:01 | 只看该作者
小弟愚钝也是STM32菜鸟。
实在是找不到在哪里看啊。数据寄存器DR吗,一直为零啊。按道理运行了下列语句之后
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);
SPI和DMA就应该已经建立连接了。剩下的工作都是硬件完成的。DMA数据发送完成之后会给个中断,但是我在哪里设断点才能看到DMA传送给SPI寄存器的数据呢?

使用特权

评论回复
6
qq65411253| | 2012-8-20 18:49 | 只看该作者
楼主的代码注释好少。虽然使用库的程序比较容易观看,但是要找到关心的内容却比较麻烦。得仔细的按顺序看代码才行,论坛上面估计没有多少人有那个耐心,当然也包括我。建议楼主尽量加上注释,可以方便查找需要的内容。
送上一份偶的驱动,你可以看看和你的有哪些不一样。我这个是已经正常工作了的。

//lcd的驱动程序,驱动芯片型号:ST7565,屏幕分辨率:128*64
#define lcd_row                                0x08u //定义lcd的行数,以字节为单位
#define lcd_column                        128u //定义lcd的列数
#define lcd_bl_port                    GPIOA //定义lcd的背光端口
#define lcd_bl_port_pos         0x00u //定义lcd的背光端口位置
#define lcd_res_port            GPIOA //定义lcd的res端口
#define lcd_res_port_pos         0x01u //定义lcd的res端口位置
#define lcd_cs_port             GPIOA //定义lcd的cs端口
#define lcd_cs_port_pos          0x02u //定义lcd的cs端口位置
#define lcd_ao_port             GPIOA //定义lcd的ao端口
#define lcd_ao_port_pos          0x03u //定义lcd的ao端口位置
#define lcd_cmddatareg                SPI1->DR //定义lcd的命令/数据寄存器
#define lcd_dma_channel         DMA1_Channel3 //定义lcd使用的dma通道,固定为DMA1_Channel3



//----------------------------------------------
//定义宏                                         
#define lcd_reset_enable                   lcd_res_port->ODR &= (~(1<<lcd_res_port_pos)) //复位lcd
#define lcd_reset_disable                   lcd_res_port->ODR |= (1<<lcd_res_port_pos) //停止复位lcd
#define lcd_ao_tocmd                           lcd_ao_port->ODR &= (~(1<<lcd_ao_port_pos)) //设置ao为命令状态
#define lcd_ao_todata                           lcd_ao_port->ODR |= (1<<lcd_ao_port_pos) //设置ao为数据状态
#define lcd_cs_enable                           lcd_cs_port->ODR &= (~(1<<lcd_cs_port_pos)) //lcd_cs使能
#define lcd_cs_disable                           lcd_cs_port->ODR |= (1<<lcd_cs_port_pos) //lcd_cs除能
#define lcd_wait_tx_end                           spi_1_wait_tx_end //等待lcd的数据/命令发送完毕
//#define lcd_wait_spi_nobusy                   spi_1_wait_nobusy //等待lcd的spi的忙标志清除,用于关闭spi模块时防止丢失最后一个字节
#define lcd_port_clk_enable                   RCC->APB2ENR|=RCC_APB2ENR_IOPAEN //使能lcd_port时钟
#define lcd_dma_enable                           lcd_dma_channel->CCR |= DMA_CCR3_EN //使能lcd_dma传输
#define lcd_dma_disable                           lcd_dma_channel->CCR &= (~DMA_CCR3_EN) //除能lcd_dma传输
#define lcd_dma_clk_enable                   RCC->AHBENR |=RCC_AHBENR_DMA1EN //使能lcd_dma的时钟
#define        lcd_dma_tcirq_enable           lcd_dma_channel->CCR |= DMA_IT_TC //使能传输完毕中断
//#define        lcd_dma_te_enable           lcd_dma_channel->CCR |= DMA_IT_TE //使能传输错误中断
#define lcd_dma_spi_txdmaen_enable SPI1->CR2|=SPI_I2S_DMAReq_Tx //使能spi的txe以启动dma
#define lcd_dma_setup_buffsize           lcd_dma_channel->CNDTR=lcd_column //设置lcd_dma的缓冲区长度
#define lcd_dma_setup_buffaddr           lcd_dma_channel->CMAR=(uint32_t)(&lcd_buff[0][0])+(lcd_dma_row*lcd_column) //设置dma的内存地址
#define lcd_dma_irqflag_clr                   DMA1->IFCR=(DMA1_IT_GL3|DMA1_IT_TC3|DMA1_IT_HT3|DMA1_IT_TE3) //清除lcd_dma中断标志
#define lcd_spi_init                                spi_1_init () //初始化lcd使用的spi



uint8_t lcd_dma_endflag=SET; //定义dma模式下数据传输结束的标志,RESET=dma未结束,SET=dma结束
uint8_t lcd_dma_row=0; //定义dma方式下lcd_showupdate函数的行指针,用来指定dma传输哪一行数据
uint8_t lcd_buff[lcd_row][lcd_column]; //定义lcd在内存中的缓冲区(即显存),128*8字节
//----------------------------------------------
//更新lcd显示的函数,基于spi模式
void lcd_showupdate_forspi (void)
{
extern uint8_t lcd_buff[8][128]; //引用显存定义
uint8_t row=0; //定义行指针
uint8_t column=0; //定义列指针

lcd_wait_tx_end; //等待lcd的数据/命令发送完毕
lcd_cs_enable; //使能lcd

for (row=0; row<lcd_row; row++) //写行数据
        {
        lcd_wait_tx_end; //等待lcd的数据/命令发送完毕
        lcd_ao_tocmd; //设置为命令状态
        lcd_cmddatareg = (row | 0xb0); //设置行指针
        lcd_wait_tx_end; //等待lcd的数据/命令发送完毕
        lcd_cmddatareg = 0x10; //发送设置列指针的命令
        lcd_wait_tx_end; //等待lcd的数据/命令发送完毕
        lcd_cmddatareg = 0; //设置列指针为0,即从0开始写入数据
        lcd_wait_tx_end; //等待lcd的数据/命令发送完毕
        lcd_ao_todata; //设置ao为数据状态

        for (column=0; column<lcd_column; column++) //写列数据
                {
                lcd_wait_tx_end; //等待lcd的数据/命令发送完毕
                lcd_cmddatareg = lcd_buff[row][column];
                }
        }
lcd_wait_tx_end; //等待lcd的最后一个数据发送完毕
lcd_cs_disable; //除能lcd
}



//----------------------------------------------
//更新lcd显示的函数,基于DMA模式,使用DMA1通道3
//如果其他程序复用DMA使用到的spi,则复用程序需要防止竞争spi总线。即复用程序中加上语句:while (lcd_dma_endflag==RESET); //等待刷新lcd完毕
void lcd_showupdate_forspi_dma (void)
{
extern uint8_t   lcd_buff[lcd_row][lcd_column]; //引用显存定义
extern uint8_t   lcd_dma_row; //引用行位置定义
DMA_InitTypeDef  lcd_dma_init_config; //定义用于初始化dma的配置
NVIC_InitTypeDef lcd_dma_irq_config; //定义用于lcd_dma的中断配置

if (lcd_dma_endflag == RESET) return; //当上一个dma没有结束时此次操作取消
lcd_dma_endflag = RESET; //清除结束标志,dma开始
lcd_dma_clk_enable; //使能dma时钟

//初始化lcd_dma的配置
lcd_dma_init_config.DMA_PeripheralBaseAddr = (uint32_t) & lcd_cmddatareg; //通道地址设置为lcd的命令/数据寄存器,需要强制转换地址为uint32_t类型
lcd_dma_init_config.DMA_MemoryBaseAddr = (uint32_t) & lcd_buff[0][0]; //通道的内存地址指向lcd_buff,需要强制转换地址为uint32_t类型
lcd_dma_init_config.DMA_DIR = DMA_DIR_PeripheralDST; //DMA方向为lcd_buff >>> lcd_cmddatareg
lcd_dma_init_config.DMA_BufferSize = lcd_column; //DMA的数据尺寸,一次传送一行数据,数据宽度为DMA_PeripheralDataSize或DMA_MemoryDataSize
lcd_dma_init_config.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //数据目的固定为lcd_cmddatareg
lcd_dma_init_config.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
lcd_dma_init_config.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度8bit
lcd_dma_init_config.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //内存数据宽度8bit
lcd_dma_init_config.DMA_Mode = DMA_Mode_Normal; //普通模式(非循环)
lcd_dma_init_config.DMA_Priority = DMA_Priority_Low; //低优先级
lcd_dma_init_config.DMA_M2M = DMA_M2M_Disable; //非内存到内存模式
DMA_Init(lcd_dma_channel, &lcd_dma_init_config); //初始化lcd的DMA通道

//初始化lcd_dma_irq的配置
lcd_dma_irq_config.NVIC_IRQChannel=DMA1_Channel3_IRQn; //指定到lcd_dma
lcd_dma_irq_config.NVIC_IRQChannelPreemptionPriority=15; //主优先级最低
lcd_dma_irq_config.NVIC_IRQChannelSubPriority=15; //从优先级最低
lcd_dma_irq_config.NVIC_IRQChannelCmd=ENABLE; //使能中断
NVIC_Init(&lcd_dma_irq_config); //配置lcd_dma中断

lcd_setup_row(lcd_dma_row=0); //设置行=0
lcd_setup_column(0); //设置列=0
lcd_dma_tcirq_enable; //使能传输完毕中断
lcd_dma_spi_txdmaen_enable; //使能spi的txe以自动启动dma
lcd_ao_todata; //设置ao为数据状态
lcd_cs_enable; //使能lcd  
lcd_dma_enable; //开始传输第一行的lcd数据
}


//-----------------------------------
//DMA1_Channel3中断函数,已经被指派给函数:lcd_showupdate_fordma
void DMA1_Channel3_IRQHandler (void)
{
lcd_wait_tx_end; //等待最后一个字节传输完毕
lcd_cs_disable; //除能lcd
lcd_dma_row++; //累计行数
lcd_dma_irqflag_clr; //清除中断标志
if (lcd_dma_row < lcd_row) //到了8行后停止dma
        {
        //重新设置lcd的行与列
        lcd_setup_row(lcd_dma_row); //设置行
        lcd_setup_column(0); //设置列=0
        lcd_ao_todata; //设置ao为数据状态
        lcd_cs_enable; //使能lcd

        //重新设置dma通道       
        lcd_dma_disable; //除能dma
        lcd_dma_setup_buffsize; //重新设置缓冲区长度
        lcd_dma_setup_buffaddr; //重新设置缓冲区地址
        lcd_dma_enable; //开始传输lcd数据
        }
else
        {
        lcd_dma_disable; //除能dma
        lcd_dma_endflag = SET; //置位结束标志,dma结束
        }
}

使用特权

评论回复
7
STM32_prog| | 2012-8-20 19:04 | 只看该作者
写的不错哦.

使用特权

评论回复
8
znsword|  楼主 | 2012-8-20 21:33 | 只看该作者
6# qq65411253
多谢!你的程序我以收藏,日后作为模版:lol。
看了一下,不同之处在于你往液晶里面写的时候是分行分次写入的,我是一股脑全写进去了。。。。。。这里面有什么差别吗?

使用特权

评论回复
9
qq65411253| | 2012-8-20 22:35 | 只看该作者
我用的这个液晶换行有专用的命令,所以不能一次性写入。你用的5110不知道有没有这个要求。

使用特权

评论回复
10
znsword|  楼主 | 2012-8-21 00:16 | 只看该作者
参考过STM32F1XX的例子,5110应该是可以一次性写入数据的。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

15

主题

43

帖子

2

粉丝