STM32 四线驱动1602A 填坑!解决重启乱码

最近学STM32,用来丰富一下生活,一个四线1602搞得一星期,对自己的智商也是醉了。填坑开始!

用的是HAL库编写的,仅仅在ODR寄存器使用了一点寄存器操作,其余全是HAL函数。

硬件接口如下:

RS    PA0         

R/W PA1

EN   PA4

 数据口D4-D7       PB4-PB7

PB11和PB14使用了两个指示灯,用来在while函数里显示系统正在运行;

V0口接一个10K的电位器,连接到板子的GND,用来调节对比度。其实之前自己也在这里跳坑了,明明显示屏已经显示出字符串了,可对比度太高了,一片白,没看出来,所以对着源码调试了好久,做了太多无用功,偶然的一次,钛合金狗眼发作,隐隐约约看出来了文字,心中出了一口长气。

然而好景不长,接着就出现了乱码的问题,只能显示部分,也就是下面的解决方法了。

现在说说里面另外需要填坑的地方,之前乱码出现的问题主要是在LCD初始化的过程中。

在LCD1602_INIT()函数中,用四线时,1602的初始化只需要高四位数据就可以完成,在初始化完成之后必须再传入四位数据,执行完“write_com(0x28);”之后液晶已经初始化,其实在执行了一半的时候就已经初始化完成,此时又传入了四位数据(一个写语句会传入8位数据),这时候如果直接写数据的话,就会形成乱码,所以需要两次功能性初始化。

源代码贴在下面了,

主函数如下:

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_hal.h"
#include "gpio.h"
#include "1602.h"
void SystemClock_Config(void);
int main(void)
{  
unsigned char dat[]={"thank you for your waiting!"};  
  HAL_Init(); 
  SystemClock_Config(); 
  MX_GPIO_Init();
  LCD1602_INIT();  
  lcd1602_show_string(0,0,dat);
 
  while (1)
  {  
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_11);
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_14);
HAL_Delay(1000);
 
  }

}


/** System Clock Configuration
*/
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
    /**Configure the Systick interrupt time 
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
    /**Configure the Systick 
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @param  None
  * @retval None
  */
void _Error_Handler(char * file, int line)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  while(1) 
  {
  }
  /* USER CODE END Error_Handler_Debug */ 
}


#ifdef USE_FULL_ASSERT
/**
   * @brief Reports the name of the source file and the source line number
   * where the assert_param error has occurred.
   * @param file: pointer to the source file name
   * @param line: assert_param error line source number
   * @retval None
   */
void assert_failed(uint8_t* file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif
/**
  * @}
  */ 
/**
  * @}
*/ 


/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

GPIO设置子程序如下,

#include "gpio.h"

void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;


  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();


  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, R_W_Pin|RS_Pin|EN_Pin, GPIO_PIN_RESET);


  /*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_0|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11|GPIO_PIN_14, GPIO_PIN_RESET);
  /*Configure GPIO pins : PAPin PAPin PAPin */
  GPIO_InitStruct.Pin = R_W_Pin|RS_Pin|EN_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


  /*Configure GPIO pins : PB4 PB5 PB6 PB7 PB11 PB14*/
  GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_11|GPIO_PIN_14;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


}


LCD显示模块函数如下,

#include "stm32f1xx_hal.h"
#include "1602.h"
#include "stm32f103xb.h"
//实际测试可用任意IO读写LCD1602//系统时钟72MHz
///4线LCD1602
//函数名:  LCD1602_INIT
//函数功能:LCD1602初始化
//说明:用四线时,1602的初始化只需要高四位数据就可以完成,在初始化完成之后必须再传入四位数据,需注意。
//      执行完write_com(0x28);之后液晶已经初始化,其实在执行了一半的时候就已经初始化完成,此时又传入了
//      四位数据(一个写语句会传入8位数据),这时候如果直接写数据的话,就会形成乱码
//注释:    DATA可以是指令或者数据
void LCD1602_INIT(void)
{
HAL_Delay(200); //等待液晶供电稳定200ms   
write_com(0x28);//4位数据总线模式,显示2行数据,5*10点阵每字符 0X38为8位数据模式, 显示2行数据,5*10点阵每字符
HAL_Delay(5);
E_H();
E_L();
write_com(0x28);//4位数据总线模式,显示2行数据,5*10点阵每字符 0X38为8位数据模式, 显示2行数据,5*10点阵每字符
HAL_Delay(5);
write_com(0x0c); //开显示,有光标,光标闪烁
HAL_Delay(5);
write_com(0x06); //写入数据光标右移,写入新数据显示屏不移动
HAL_Delay(5);
write_com(0x01); //清屏
HAL_Delay(5);
}
//函数名:LCD_1602_READY
//函数功能:检测PB7是否为高电平,忙碌状态;
//注释:
void LCD_1602_READY(void)
{
uint8_t sta;
RS_L();
RW_H();
do
{
E_H();
HAL_Delay(5);
sta = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7);
HAL_Delay(5);
E_L();
HAL_Delay(5);
} while ( sta & 0x80);

}
//函数名:  LCD1602_Clear_Screen
//函数功能:LCD1602清屏
//注释:   
void LCD1602_Clear_Screen(void)
{
LCD1602_DATA_write(Write_order,0x01); //清屏
}


//函数名:  write_com
//函数功能:LCD1602写指令
//注释:    
void write_com(unsigned char dat)
{
  unsigned char j;
LCD_1602_READY();
  RS_L();//指令
RW_L();//写操作模式
E_L();
  for(j=0;j<2;j++)
  {
GPIOB->ODR =(dat&0x00F0);
E_H();
HAL_Delay(5);
E_L();//允许     
  dat<<=4;
HAL_Delay(5);
  }

}
//函数名:  write_data
//函数功能:LCD1602写数据
//注释:   
void write_data(unsigned char dat)
{
  unsigned char j;
LCD_1602_READY();
  RS_H();//数据
RW_L();//写操作模式
E_L();
  for(j=0;j<2;j++)
  {
GPIOB->ODR =(dat&0x00F0);
E_H();
HAL_Delay(5);
  E_L();   
  dat<<=4;
HAL_Delay(5);
  }

}

//函数名:lcd1602_write
//作用:将数据或者指令写入LCD1602
//注释:
void LCD1602_DATA_write(LCD1602_Write_TypeDef order,unsigned char dat)
{
if(order==Write_data)
write_data(dat);
else
  write_com(dat);
}


//函数名:lcd1602_show_character
//作用:  在制定坐标,显示单个字符
void lcd1602_show_character(unsigned char x,unsigned char y,unsigned char dat)
{
unsigned char address;
x=x%16;
y=y%2;
if(y==1)
address=0xc0+x;
else
address=0x80+x;  
LCD1602_DATA_write(Write_order,address);
LCD1602_DATA_write(Write_data,dat);
}


//函数名:lcd1602_show_number
//作用:
//     以(X,Y)坐标为起始,显示一个数字(此数字值不能大于4294967295(0xffffffff))
//注释:
//     返回数字的显示长度,若改行显示不下,函数自动换行, 显示其余部分
unsigned char lcd1602_show_number(unsigned char x,unsigned char y,unsigned short dat)
{
unsigned short pow=1,instead;
unsigned char increase=0,lengh=0;
instead=dat;
while(instead!=0)
{
instead=instead/10;
increase++;
}
lengh=increase;
if(increase==0)
{
lcd1602_show_character(x,y,0x30);
return 1;
}
else
{
for(;increase>1;increase--)
pow=pow*10;
while(pow!=0)
{
instead=dat/pow;
lcd1602_show_character(x,y,(0x30+instead));
x++;
if(((x%16)==0)&&(x!=0))
{
y++;
y=y%2;
x=x%16;
}
dat=dat%pow;
pow=pow/10;  
  }
}
return lengh;
}
//函数名:lcd1602_show_string
//作用:
//     以(X,Y)坐标为起始,显示一个字符串
//注释:
//     返回数字的显示长度,若改行显示不下,函数自动换行, 显示其余部分,字符长度小于256
unsigned char lcd1602_show_string(unsigned char x,unsigned char y,unsigned char *dat)
{
unsigned char lengh=0;
while(dat[lengh]!='\0')
{
if(((x%16)==0)&&(x!=0))
{
    y++;
y=y%2;
    x=x%16;
}
    lcd1602_show_character(x,y,dat[lengh]);
x++;
lengh++; 
}
return (lengh);
}
//函数名:lcd1602_show_number
//作用:
//     以(X,Y)坐标为起始,显示一个数字(此数字值不能大于4294967295(0xffffffff))
//注释:
//     返回数字的显示长度,若改行显示不下,函数自动换行, 显示其余部分
unsigned char lcd1602_show_s32(unsigned char x,unsigned char y,short dat)
{
short pow=1,instead;
unsigned char increase=0,lengh=0;
if(((dat&0x80000000)==0x80000000)&&(dat!=0xffffffff))//负数
{
  instead=-dat;
  y=y%2;
  lcd1602_show_character(x,y,0x2D);//"-"
  x++;
}
else if((dat&0x80000000)!=0x80000000)//正数
{
  instead=dat;
  y=y%2;
  lcd1602_show_character(x,y,0X2B);//"+"
  x++;     
}
else//0
{
  instead=0;   
}
dat=instead; 
while(instead!=0)
{
instead=instead/10;
increase++;
}
lengh=increase;
if(increase==0)
{
lcd1602_show_character(x,y,0x30);
return 1;
}
else
{
for(;increase>1;increase--)
pow=pow*10;
while(pow!=0)
{
instead=dat/pow;
lcd1602_show_character(x,y,(0x30+instead));
x++;
if(((x%16)==0)&&(x!=0))
{
y++;
y=y%2;
//x=x%16;
}
dat=dat%pow;
pow=pow/10;  
  }
}
return lengh+1;
}

LCD头文件就是定义了上面的函数:

#ifndef __1602_H__
#define __1602_H__

typedef enum
{
Write_data  =0X00,
Write_order =0x01/*BIT0*/ 
}LCD1602_Write_TypeDef;


#define RW_L() GPIOA->ODR &=~(0x01<<1)//RW=0,PA1
#define RW_H() GPIOA->ODR |=(0x01<<1)//RW=1


#define RS_L() GPIOA->ODR &=~(0x01)//RS=0,PA0
#define RS_H() GPIOA->ODR |=(0x01)//RS=1


#define E_L()  GPIOA->ODR &=~(0x01<<4)//E=0,PA4
#define E_H()  GPIOA->ODR |=(0x01<<4)//E=1


//函数名:  LCD1602_INIT
//函数功能:LCD1602初始化
//注释:    DATA可以是指令或者数据
void LCD1602_INIT(void);


//函数名:  LCD1602_Clear_Screen
//函数功能:LCD1602清屏
//注释:   
void LCD1602_Clear_Screen(void);

//函数名:  write_com
//函数功能:LCD1602写指令
//注释:    
void write_com(unsigned char dat);
//函数名:  write_data
//函数功能:LCD1602写数据
//注释:   
void write_data(unsigned char dat);

//函数名:lcd1602_write
//作用:将数据或者指令写入LCD1602
//注释:
void LCD1602_DATA_write(LCD1602_Write_TypeDef order,unsigned char dat);
//函数名:lcd1602_show_character
//作用:  在制定坐标,显示单个字符
void lcd1602_show_character(unsigned char x,unsigned char y,unsigned char dat);
//函数名:lcd1602_show_number
//作用:
//     以(X,Y)坐标为起始,显示一个数字(此数字值不能大于4294967295(0xffffffff))
//注释:
//     返回数字的显示长度,若改行显示不下,函数自动换行, 显示其余部分
unsigned char lcd1602_show_number(unsigned char x,unsigned char y,unsigned short dat);

//函数名:lcd1602_show_string
//作用:
//     以(X,Y)坐标为起始,显示一个字符串
//注释:
//     返回数字的显示长度,若改行显示不下,函数自动换行, 显示其余部分,字符长度小于256
unsigned char lcd1602_show_string(unsigned char x,unsigned char y,unsigned char *dat);

//显示有符号S32
unsigned char lcd1602_show_s32(unsigned char x,unsigned char y,short dat);
//为了适应任意IO驱动LCD1602做的定义
//读忙操作
void LCD_1602_READY(void);
//配置GPIO端口
void GPIO_Configuration(void);
#endif















































































  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是一个简单的STM32F103RCT6驱动LCD1602A的代码示例,使用了4位数据线模式,你可以根据自己的需要进行修改: ```c #include "stm32f10x.h" #define RS GPIO_Pin_1 #define EN GPIO_Pin_2 #define D4 GPIO_Pin_4 #define D5 GPIO_Pin_5 #define D6 GPIO_Pin_6 #define D7 GPIO_Pin_7 void delay_us(uint32_t us) // 延时函数 { uint32_t i; for(i=0;i<us*8;i++); } void delay_ms(uint32_t ms) // 延时函数 { uint32_t i; for(i=0;i<ms*8000;i++); } void GPIO_Configuration(void) // GPIO初始化函数 { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 打开GPIOA时钟 GPIO_InitStructure.GPIO_Pin = RS | EN | D4 | D5 | D6 | D7; // 配置引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA } void LCD_write_data(uint8_t data) // 写入数据 { GPIO_SetBits(GPIOA, RS); // RS为高电平,表示写入数据 GPIO_WriteBit(GPIOA, D4, (BitAction)((data>>0)&0x01)); // 输出D4 ~ D7 GPIO_WriteBit(GPIOA, D5, (BitAction)((data>>1)&0x01)); GPIO_WriteBit(GPIOA, D6, (BitAction)((data>>2)&0x01)); GPIO_WriteBit(GPIOA, D7, (BitAction)((data>>3)&0x01)); GPIO_SetBits(GPIOA, EN); // 使能 delay_us(1); GPIO_ResetBits(GPIOA, EN); // 禁止 delay_us(40); // 等待液晶屏处理数据 } void LCD_write_cmd(uint8_t cmd) // 写入命令 { GPIO_ResetBits(GPIOA, RS); // RS为低电平,表示写入命令 GPIO_WriteBit(GPIOA, D4, (BitAction)((cmd>>0)&0x01)); // 输出D4 ~ D7 GPIO_WriteBit(GPIOA, D5, (BitAction)((cmd>>1)&0x01)); GPIO_WriteBit(GPIOA, D6, (BitAction)((cmd>>2)&0x01)); GPIO_WriteBit(GPIOA, D7, (BitAction)((cmd>>3)&0x01)); GPIO_SetBits(GPIOA, EN); // 使能 delay_us(1); GPIO_ResetBits(GPIOA, EN); // 禁止 delay_us(40); // 等待液晶屏处理数据 } void LCD_init(void) // 液晶屏初始化函数 { LCD_write_cmd(0x02); // 4位数据线模式 LCD_write_cmd(0x28); // 2行,5×8点阵字符 LCD_write_cmd(0x0c); // 开启显示, 光标关闭 LCD_write_cmd(0x06); // 文本写入后光标右移 LCD_write_cmd(0x01); // 清屏,将光标移动到起始位置 } void LCD_printf(char *str) // 字符串输出函数 { while(*str) { LCD_write_data(*str++); } } int main(void) { GPIO_Configuration(); // GPIO初始化 LCD_init(); // LCD初始化 while(1) { LCD_write_cmd(0x80); // 光标移动到第一行起始位置 LCD_printf("Hello, world!"); // 输出字符串 delay_ms(1000); // 延时1秒 LCD_write_cmd(0x01); // 清屏 delay_ms(500); // 延时0.5秒 } } ``` 注意,这里使用的是GPIOA口,如果你使用了其他口,请根据需要进行修改。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值