STM32开发,串口和PC机通信(串口中断、FIFO机制),安富莱+正点原子程序合并

STM32开发,串口和PC机通信(串口中断、FIFO机制),安富莱+正点原子程序合并

  • 1 概述
    • 1.1 资源概述
    • 1.2 实现功能
  • 2 软件实现
    • 2.1实现步骤
    • 2.2 main()函数代码
    • 2.3 正点原子键盘连按和不连按函数说明
    • 2.4 安富莱FIFO按键程序说明
      • 2.4.1 程序分析
    • 2.5系统初始化分析
  • 3 实验结果

1 概述

实验的代码已经上传,无需积分。

1.1 资源概述

开发板:正点原子STM32F103 Nano开发板
CUBEMX版本:1.3.0
MDK版本:5.27
主控芯片型号:STM32F103RBT6
正点原子开发板

1.2 实现功能

1,USB连接到电脑。运行串口 软件。
2,操作开发板上的按键,可以在PC机串口软件上观察到变化的数字。对应的LED灯翻转。
3,在计算机键盘上输入数字键1、2、3、4可以分别控制开发板上的4个LED指示灯。

2 软件实现

2.1实现步骤

此次调试时发现安富莱自带的bsp_key并不是太好用,也可能时我没改好,怎么改,有两个按键都无法对应上,另外两个对应上的按键逻辑很奇怪。于是舍弃安富莱的按键程序,将正点原子的按键程序移植过来。移植过程如下:
正点原子键盘支持包:key.c,key.h
安富莱键盘支持包:bsp _key.c,bsp_key.h
1,同时打开安富莱的工程和正点原子的工程,分别编译通过。
2,将正点原子的key.c文件和Key.h文件直接塞到安富莱的工程中。
3,通过正点原子的工程分别找到key文件中用到的函数的定义以及相关宏定义,打开对应的文件,copy。
4,将copy的内容粘贴到安富莱工程中的key文件中。
5,编译,并将错误一个个找到原因并解决。
6,在安富莱工程中,定位到bsp_key文件,查找里边的函数使用处,将使用到的函数,全部替换为新增加的正点原子key文件中的函数。
7,编译,解决报错问题,直至没有报错。
8,从安富莱工程中移除bsp_key文件,再次编译,直至改掉所有error和warning。
9,重新定义正点原子的GPIO口等,修改安富莱相关端口定义,不局限于GPIO口,串口等。
上述步骤切记要一步一步来,慢慢改,一步到位很容易出错,且错误一环套一环非常难查找。反而耽误时间。
补充说明
后续在移植野火的程序时,出现了同样的情况,按键模块不好使,经过一步步替代简化对比后找到了原因,在GPIO初始化时,按键上下拉电阻需要配置,如果时输入低电平有效,则配置为上拉,如果时输入高电平有效,则配置为下拉。否则将出现逻辑混乱。改好后,程序也实现了预期的功能。
STM32开发,串口和PC机通信(串口中断、FIFO机制),安富莱+正点原子程序合并_第1张图片

2.2 main()函数代码

main()函数代码如下,键盘相关部分函数借用于正点原子,其它使用安富莱自己的函数。

/*
*********************************************************************************************************
*
*	模块名称 : 主程序入口
*	文件名称 : main.c
*	版    本 : V1.0
*	说    明 : 串口通信例子,和PC机超级终端软件进行交互
*	修改记录 :
*		版本号  日期       作者    说明
*		V1.0    2015-08-30 armfly  首发
*
*	Copyright (C), 2015-2016, 安富莱电子 www.armfly.com
*
*********************************************************************************************************
*/

#include "bsp.h"				/* 底层硬件驱动 */


/* 定义例程名和例程发布日期 */
#define EXAMPLE_NAME	"V4-003_串口和PC机通信(串口中断、FIFO机制)"
#define EXAMPLE_DATE	"2020-04-24"
#define DEMO_VER		"1.0"
#define key_mode 0		/*0为按键不连续,1为按键连续*/
static void PrintfLogo(void);
static void PrintfHelp(void);

/*
*********************************************************************************************************
*	函 数 名: main
*	功能说明: c程序入口
*	形    参:无
*	返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
	uint8_t ucKeyCode;	
	int16_t count = 0;
	uint8_t fRefresh = 0;
	uint8_t read;
	
	/*
		ST固件库中的启动文件已经执行了 SystemInit() 函数,该函数在 system_stm32f4xx.c 文件,主要功能是
	配置CPU系统的时钟,内部Flash访问时序,配置FSMC用于外部SRAM
	*/
	bsp_Init();		/* 硬件初始化 */
	
	PrintfLogo();	/* 打印例程名称和版本等信息 */
	PrintfHelp();	/* 打印操作提示 */
	
	fRefresh = 1;
	/* 主程序大循环 */
	while (1)
	{
		bsp_Idle();		/* CPU空闲时执行的函数,在 bsp.c */
	
		/* 摇杆左右键(上下键)控制LED流动 */
		if (fRefresh)
		{
			fRefresh = 0;

			#if 0	/* 可以用 printf打印数据到串口 */
				printf("count = %6d \r\n", count);
			#else	/* 也可以用 sprintf先输出到一个buf,然后在发送到串口 */
			{
				char buf[64];
								
				sprintf(buf, "count = %6d \r\n", count);
				
				comSendBuf(COM1, (uint8_t *)buf, strlen(buf));
			}
			#endif
		}
		
		if (comGetChar(COM1, &read))
		{
			switch (read)
			{
				case '1':
					bsp_LedToggle(1);
					break;

				case '2':
					bsp_LedToggle(2);
					break;

				case '3':
					bsp_LedToggle(3);
					break;

				case '4':
					bsp_LedToggle(4);
					break;	
			}

		}
		
		/* 处理按键事件 */
		ucKeyCode = KEY_Scan(key_mode);
		if (ucKeyCode != 0)
		{
			/* 有键按下 */
			switch (ucKeyCode)
			{
				case KEY0_PRES:		/* 按键0被按下 */
					count-=10;
					fRefresh = 1;
					bsp_LedToggle(1);
					break;		
				
				case KEY1_PRES:		/* 按键1按下 */
					count--;		
					fRefresh = 1;
					bsp_LedToggle(2);
					break;

				case KEY2_PRES:		/* 按键2按下 */
					count+=10;		
					fRefresh = 1;
					bsp_LedToggle(3);
					break;				
				
				case WKUP_PRES:		/* 按键WKUP按下 */
					count++;
					fRefresh = 1;
					bsp_LedToggle(4);
					break;
								
				default:
					break;
			}
		}
	}
}

/*
*********************************************************************************************************
*	函 数 名: PrintfHelp
*	功能说明: 打印操作提示
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
static void PrintfHelp(void)
{
	printf("操作提示:\r\n");
	printf("1. 摇杆控制count计数值\r\n");
	printf("     上键 = +1\r\n");
	printf("     下键 = -1\r\n");		
	printf("     左键 = -10\r\n");
	printf("     右键 = +10\r\n");
	printf("2. PC上输入数字1-4控制开发板上的LED指示灯\r\n");
}

/*
*********************************************************************************************************
*	函 数 名: PrintfLogo
*	功能说明: 打印例程名称和例程发布日期, 接上串口线后,打开PC机的超级终端软件可以观察结果
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
static void PrintfLogo(void)
{
	printf("*************************************************************\r\n");
	printf("* 例程名称   : %s\r\n", EXAMPLE_NAME);	/* 打印例程名称 */
	printf("* 例程版本   : %s\r\n", DEMO_VER);		/* 打印例程版本 */
	printf("* 发布日期   : %s\r\n", EXAMPLE_DATE);	/* 打印例程日期 */

	/* 打印ST固件库版本,宏定义在 stm32f4xx.h 文件 */
	printf("* 固件库版本 : %d.%d.%d\r\n", __STM32F10X_STDPERIPH_VERSION_MAIN,
			__STM32F10X_STDPERIPH_VERSION_SUB1,__STM32F10X_STDPERIPH_VERSION_SUB2);

	/* 打印 CMSIS 版本. 宏定义在 core_cm4.h 文件 */
	printf("* CMSIS版本  : %X.%02X\r\n", __CM3_CMSIS_VERSION_MAIN, __CM3_CMSIS_VERSION_SUB);

	printf("* \r\n");	/* 打印一行空格 */
	printf("* QQ    : 1295744630 \r\n");
	printf("* Email : [email protected] \r\n");
	printf("* Copyright www.armfly.com 安富莱电子\r\n");
	printf("*************************************************************\r\n");
}

/***************************** 安富莱电子 www.armfly.com (END OF FILE) *********************************/

2.3 正点原子键盘连按和不连按函数说明

u8 KEY_Scan(u8 mode)
{	 
	static u8 key_up=1;//按键按松开标志
	if(mode)key_up=1;  //支持连按		  
	if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
	{
		bsp_DelayMS(10);//去抖动 
		key_up=0;
		if(KEY0==0)return KEY0_PRES;
		else if(KEY1==0)return KEY1_PRES;
		else if(KEY2==0)return KEY2_PRES;
		else if(WK_UP==1)return WKUP_PRES; 
	}else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1; 	     
	return 0;// 无按键按下
}

第三行,定义了一个静态变量,static,这个函数在程序运行时,第一次调用函数时,后面跟着key_up=1才有效。后面不管调用了多少次此定义的key_up=1不再生效,即使被其它地方赋值了其它数。通过这个功能实现了连按和不连按的功能。实验证实,当static这个类型删除后,不管mode是0还是1,都是连按。

2.4 安富莱FIFO按键程序说明

后来在8月份,回看程序时,把安富莱的调通了,这个按键的思想不一样,十分值得借鉴,设计按键FIFO主要有三个方面的好处:

  1. 可以有效的记录按键事件的发生,特别是需要实现按键的按下,长按,弹起等事件,使用FIFO的方式来实现是一种非常好的思路。
  2. 系统是非阻塞的,这样系统在检测到按键按下的情况下,由于机械按键抖动的原因不需要在这里等待一段时间,然后再确定按键是否按下。
  3. 按键FIFO程序在嘀嗒定时器中定期的执行检测,不需要在主程序中一直做检测,这样可以有效的降低系统资源消耗。

从裸机的角度分析
中断方式:中断方式可以有效的检测到按键按下的消息,并执行相应的程序,但是用中断方式实现按键FIFO相对就有点麻烦,如果是V5开发板这样每个按键都是独立的接一个IO引脚,需要我们给每个IO都设置一个中断,程序中过多的中断会影响系统的稳定性。
查询方式:查询方式有一个最大的缺点就是需要程序定期的去执行查询,耗费一定的系统资源,实际上耗费不了多大的系统资源,因为这种查询方式也只是查询按键是否按下,按键事件的执行还是在主程序里面实现。
从OS的角度分析
中断方式:在OS中要尽可能少用中断方式,因为在RTOS中过多的使用中断会影响系统的稳定性和可预见性(抢占式调度的OS基本没有可预见性,基于时间触发调度的可预见性要好很多,这方面知识会在RTOS专题中跟大家详细讲解)。比较重要的事件处理需要用中断的方式。
查询方式:对于用户按键推荐使用这种查询方式来实现,现在的OS基本都带有CPU利用率的功能,这个按键FIFO占用的还是很小的,基本都在%1以下。

2.4.1 程序分析

按键的检测程序被嘀嗒定时器中断每10ms调用一次

void SysTick_Handler(void)
{
	SysTick_ISR();
}

这个SysTick_ISR()函数里边有这么一个函数

bsp_RunPer10ms();	/* 每隔10ms调用一次此函数,此函数在 bsp.c */

在bsp_RunPer10ms()里边调用了这么一个函数

void bsp_RunPer10ms(void)
{
	bsp_KeyScan();		/* 按键扫描 */
}

在bsp_KeyScan()中有这么一个函数

void bsp_KeyScan(void)
{
	uint8_t i;

	for (i = 0; i < KEY_COUNT; i++)
	{
		bsp_DetectKey(i);
	}
}

在bsp_DetectKey(i)中有这么一个赋值语句

	pBtn = &s_tBtn[i];
	if (pBtn->IsKeyDownFunc())
	{

在bsp_InitKeyVar(void)中有这么一个语句

	/* 判断按键按下的函数 */
	s_tBtn[0].IsKeyDownFunc = IsKeyDown1;
	s_tBtn[1].IsKeyDownFunc = IsKeyDown2;
	s_tBtn[2].IsKeyDownFunc = IsKeyDown3;
	s_tBtn[3].IsKeyDownFunc = IsKeyDown4;

	/* 组合键 */
	s_tBtn[4].IsKeyDownFunc = IsKeyDown9;
	s_tBtn[5].IsKeyDownFunc = IsKeyDown10;

宏定义如下

	static uint8_t IsKeyDown1(void) {if ((GPIO_PORT_K1->IDR & GPIO_PIN_K1) == 0) return 1;else return 0;}
	static uint8_t IsKeyDown2(void) {if ((GPIO_PORT_K2->IDR & GPIO_PIN_K2) == 0) return 1;else return 0;}
	static uint8_t IsKeyDown3(void) {if ((GPIO_PORT_K3->IDR & GPIO_PIN_K3) == 0) return 1;else return 0;}
	static uint8_t IsKeyDown4(void) {if ((GPIO_PORT_K4->IDR & GPIO_PIN_K4) == 0) return 1;else return 0;}

	static uint8_t IsKeyDown9(void) {if (IsKeyDown1() && IsKeyDown2()) return 1;else return 0;}
	static uint8_t IsKeyDown10(void) {if (IsKeyDown1() && IsKeyDown2()) return 1;else return 0;}

通过这么一长串的操作,每隔10ms,将按键值写进了数组中
主程序,读取按键值时,使用时只需要去读数组中的值即可

ucKeyCode = bsp_GetKey();	/* 读取键值, 无键按下时返回 KEY_NONE = 0 */

读取函数如下

uint8_t bsp_GetKey(void)
{
	uint8_t ret;

	if (s_tKey.Read == s_tKey.Write)
	{
		return KEY_NONE;
	}
	else
	{
		ret = s_tKey.Buf[s_tKey.Read];

		if (++s_tKey.Read >= KEY_FIFO_SIZE)
		{
			s_tKey.Read = 0;
		}
		return ret;
	}
}

流程如下图
STM32开发,串口和PC机通信(串口中断、FIFO机制),安富莱+正点原子程序合并_第2张图片
函数调用关系图如下
STM32开发,串口和PC机通信(串口中断、FIFO机制),安富莱+正点原子程序合并_第3张图片

2.5系统初始化分析

在正点原子的例程中main函数中有一个Stm32_Clock_Init的函数对时钟进行配置。但是安富莱的没有,原因是安富莱直接借用了系统文件里边的初始化。在启动文件.s中有下述代码,在系统启动时,会调用SystemInit函数。

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP

系统文件为system_stm32f10x.c中有

void SystemInit (void)

在这个函数中有下述函数,在正点原子的例程中,将这个函数删掉了

  SetSysClock();

在这个函数下有这个函数

  SetSysClockTo72();

在这个函数中对各个时钟分频进行了配置,实现了系统时钟的配置工作。正点原子将这部分整合到了单独的一个函数中在main函数中进行了调用

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* Wait till HSE is ready and if Time out is reached exit */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  if (HSEStatus == (uint32_t)0x01)
  {
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

 
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#ifdef STM32F10X_CL
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
        
    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
#else    
    /*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */

    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* If HSE fails to start-up, the application will have wrong clock 
         configuration. User can add here some code to deal with this error */
  }
}

3 实验结果

串口打印结果
STM32开发,串口和PC机通信(串口中断、FIFO机制),安富莱+正点原子程序合并_第4张图片
发送1234时,全部的四个灯点亮
再次发送1234时,四个灯全灭

按按键时,对应的LED状态会进行翻转

你可能感兴趣的:(STM32开发学习笔记,stm32,嵌入式)