首先能你需要自己做一块最小系统,因为计时精度不需要太高,所以为了节省成本可以省去晶振,直接使用芯片内部的时钟。这是我的原理图采用的是stm32f031c6t6某宝5块钱,
硬件部分
?
?然后你可根据你的按键的模具画一块孔位匹配大小相等的pcb,并选用按键,我这边使用的是薄膜按键,当然你也可以使用机械按键或者更好的。最终得到pcb
软件部分
使用stm32cubemx进行辅助开发首先先勾选 “debug serial wire”(这非常重要不然生成的工程烧录后就会出现烧写一次后无法烧写)
?然后正常的设置gpio口的模式因为这边我使用的是5x4所以需要使用9个io口,且使用4个io口为输出,5个引脚为输入检测。io口配置为下图
?然后在配置串口1实现串口1的收发模式找到串口1 mode选择asynchronous。
?并配置相关的串口参数,一般就是改改波特率这是我的串口配置
?如何选择clock configuration 进行时钟配置如下图
?如何配置定时器进行超时判定,这里使用的是定时器2,这里呢涉及预分频和重装值,通过对时钟进行分频获得我们想要的频率,就是多少秒计一个数,以及什么时候触发计数,什么时候触发重装,触发定时器中断执行我们想要的程序。其中prescaler为预防频值这里是4799,把48mhz进行分频,48000000(48m)/(4799+1)=10000(10khz)根据频率公式:f=1/T ,t=1/f=0.1ms意思是每0.1ms电平变化一次,而我这边选择的模式为上升沿计数,便是下方的counter mode 选择为up。而重装值则为你需要的时间:T / t(计数的时间)-1,如我这边需要300ms 所以得到的period(重装值)就为2999.
?配置完后就可以进行代码的生成了选择 project manager,选择你想要的文件夹,和代码编译器
我这里选择的是keil 5 然后点击右上角的 GENERATE CODE 进行生成
?然后就可以开始写逻辑功能了
串口配置
首先可以先写个把C语言的printf给放到我们的串口中,不用也可以,然后在stm32f0xx.hal.c中加入下面的代码,串口发送定义到回串口1
#include <stdio.h>
extern UART_HandleTypeDef huart1; //声明串口
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/**
* 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
且在主函数的文件中添加宏定义,好定义一个数组
#define RXBUFFERSIZE 256
char RxBuffer[RXBUFFERSIZE];
并在编译出来的main函数中,添加定时器中断函数。便可进行测试,定时器,与串口发送是否正常
主函数中的while(1)死循环中添加
HAL_TIM_Base_Start_IT(&htim2);//开启定时器2
在main.c文件的末尾添加以下定时器中断函数。
/*******************定时器中断*******/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM2)
{
printf("test,test\n");
__HAL_TIM_CLEAR_FLAG(&htim2,TIM_SR_UIF);//清除tim2的中断标志位
}
}
烧录到芯片后,便会定时发送 test ,我这便为300ms发送一次,倘若因为串口发送太快,亦可使用吧重装值preiod 调到29999,使其3s发送一次,便可大概知道代码是否正常。
代码段在以下此段中断period的值
?当成功后便可开始写
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?按键键测
按键扫描有许多种写法,如比较老实的使用 :if else if 或者使用 获取io寄存器的值进行取值与swich判断便可(当然你要明白他代表的意义)
要写逻辑,首先呢便要搞清楚你需要的逻辑,按键矩阵按键扫描方式我这里有两种,(当然会有其他我没想到的方式)我这 写的有 :
方法一,很简单通过单独对每一行进行输出,并进行io口的扫描,得到你输出的行数,与扫描列得的列数可知道几行几列。并赋值(简单)当然你也可以通过寄存器简化,代码就不用写这么多,这就是为什么说寄存器运行比(不管是标准库还是hal库,不接受反驳)库函数快的原因。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?实现代码为
char KeyPad_Scan()
{
u16 readvalue = 0;
char data1=0;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_RESET);//
HAL_Delay(2);
readvalue =GPIOA->IDR; //读取gpioA所有的引脚
readvalue &= 0x01f0; //保留低8位的高5位的值
if(readvalue != 0x01f0) //高5位引脚有一个被按下
{
HAL_Delay(5);
readvalue =GPIOA->IDR; //读取gpioA所有的引脚
readvalue &= 0x01f0; //保留低8位的值
if(readvalue != 0x01f0) //高5位引脚有一个被按下
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_SET);//
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_Delay(2);
readvalue =GPIOA->IDR; //读取gpioA所有的引脚
readvalue &= 0x01f0; //保留低8位的值
if(readvalue != 0x01f0) //高5位引脚有一个被按下
{
HAL_Delay(2);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)==0 )
{
data1 = '1';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0)
{
data1 = '4';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_6)==0)
{
data1 = '7';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_7)==0)
{
data1 = '0';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==0)
{
data1 = 'h';
}
}else{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
HAL_Delay(2);
readvalue =GPIOA->IDR; //读取gpioA所有的引脚
readvalue &= 0x01f0; //保留低8位的值
if(readvalue != 0x01f0) //高5位引脚有一个被按下
{
HAL_Delay(2);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)==0 )
{
data1 = '2';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0)
{
data1 = '5';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_6)==0)
{
data1 = '8';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_7)==0)
{
data1 = '.';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==0)
{
data1 = 'g';
}
}else{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(2);
readvalue =GPIOA->IDR; //读取gpioA所有的引脚
readvalue &= 0x01f0; //保留低8位的值
if(readvalue != 0x01f0) //高5位引脚有一个被按下
{
HAL_Delay(2);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)==0 )
{
data1 = '3';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0)
{
data1 = '6';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_6)==0)
{
data1 = '9';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_7)==0)
{
data1 = 'e';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==0)
{
data1 = 'f';
}
}else{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_Delay(2);
readvalue =GPIOA->IDR; //读取gpioA所有的引脚
readvalue &= 0x01f0; //保留低8位的值
if(readvalue != 0x01f0) //高5位引脚有一个被按下
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)==0 )
{
data1 = 'a';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5)==0)
{
data1 = 'b';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_6)==0)
{
data1 = 'c';
}
else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_7)==0)
{
data1 = 'd';
}
}
}
}
}
}
}
if(data1!=0) //此处为把值存储在一个数组里,并把按键对应值保存在数组里
{
for(char j;j<5;j++)
{
if (data[j]==0)
{ data[j]=data1;
return data1;
}
}
}
return data1;
;
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
}
方式二,先把行(则是上面原理图中的x1234其代表的io)进行?置高输出,然后列(则是上面原理图的y12345其代表的io)进行io扫描,那一列变为高电平则为此列有效,把数据保存,在把列进行置高输出,然后行进行io扫描,哪一行为高电平则此行有效。(逻辑可能有点乱,但逻辑是这个逻辑) 提示(内含一下简单的寄存器操作,学过51虽然我没学过应该比较容易理解)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 实现代码为
short KeyPad_Scan()
{
u16 readvalue = 0;
char data;
u16 re=0;
u16 re1=0;
u16 re2=0;
KeyPad_Init1(); //PA0~3上拉PA4~8下拉
readvalue =GPIOA->IDR; //读取gpioA所有的引脚
readvalue &= 0x01ff; //保留低8位的值
if(readvalue != 0x000f) //高5位引脚有一个被按下
{
HAL_Delay(10);//按键消抖10ms
readvalue =GPIOA->IDR; //读取gpioA所有的引脚
if(readvalue != 0x000f)
{
re1 = GPIOA->IDR; //读取gpioA所有的引脚
re1 &= 0x01f0; //保留PA4~PA8
KeyPad_Init2(); //PA4~8上拉,PA0~3下拉
HAL_Delay(1);//经我测试,这里延迟50ms反应最快而很少出现不反应的状况
re2 =GPIOA->IDR; //读取gpioA所有的引脚
re2 &= 0x000f; //保留PA0-PA3的值
//
re=re1|re2; //相与,就知道哪一行那一列被按下
switch(re)
{
case 0x0011: data='1';break;//以下为把按键对应的值赋予
case 0x0012: data='2';break;
case 0x0014: data='3';break;
case 0x0018: data='a';break;
case 0x0021: data='4';break;
case 0x0022: data='5';break;
case 0x0024: data='6';break;
case 0x0028: data='b';break;
case 0x0041: data='7' ;break;
case 0x0042: data='8' ;break;
case 0x0044: data='9' ;break;
case 0x0048: data='c';break;
case 0x0081: data='0' ;break;
case 0x0082: data='.' ;break;
case 0x0084: data='f' ;break;
case 0x0088: data='d';break;
case 0x0101: data = 'h';break;
case 0x0102: data='g';break;
case 0x0104: data='e';break;
default: return 1;
}
return 0;
}
}
return 1;
}
?这其中有有两个函数的定义如下
void KeyPad_Init2()
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8, GPIO_PIN_SET);
/*Configure GPIO pins : PA0 PA1 PA2 PA3 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN ;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PA4 PA5 PA6 PA7
PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void KeyPad_Init1()
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, GPIO_PIN_SET);
/*Configure GPIO pins : PA0 PA1 PA2 PA3 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP ;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PA4 PA5 PA6 PA7
PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
以上便是按键扫描部分。(方法一和方法二选其一便可)
因为考虑接收的芯片的接收中断,我们不可让值一直发,使用我们要使得按键弹起时便不再发送,得写个下面的代码在主函数的死循环中
aaaa:
while((GPIOA->IDR & 0x01f0)!= 0x01f0)HAL_Delay(5); //读取gpioA所有的引脚
HAL_Delay(10);
if((GPIOA->IDR & 0x01f0 )!= 0x01f0)goto aaaa; //读取gpioA所有的引脚
以下为串口协议部分,因为很多时候都不单单的只发数值,要考虑数据错误,以及接收方接收到的数据是否完整,所以需要使用校验这就出现了很多校验方法(什 么 crc等等)这里就不讲了,我这里的校验单纯只是把我自身提前的定义?数据头 如我这里的字符‘k’,然后跟着的是数据,然后是数据发送次数,已经把前面全部相加得到的校验数值。 如图下(我这里是把串口发送也放在一起了)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
目录
硬件部分
软件部分
串口配置
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?按键键测
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
?串口协议
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 接收中断? ?
?串口协议
char data_send(char num,char data)
{
char i;
char check;
senddata[4]=0;
senddata[0]='k';
senddata[1]=data;
senddata[3]=(((num&0xf)>9)?((num&0xf)-9+'a'):((num&0xf)+'0'));
senddata[2]=((((num>>4)&0xf)>9)?(((num>>4)&0xf)-9+'a'):(((num>>4)&0xf)+'0'));
// check = 'k'+data+(((num&0xf)>9)?((num&0xf)-9+'a'):((num&0xf)+'0'))+((((num>>4)&0xf)>9)?(((num>>4)&0xf)-9+'a'):(((num>>4)&0xf)+'0'));
check = senddata[0]+senddata[1]+senddata[2]+senddata[3];
senddata[4]=check;
for(i=0;i<5;i++)printf("%c",senddata[i]);
// printf(senddata);
// printf("\n");
return check;
}
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 以下为我的
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 接收中断? ?
char c;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file
*/
if(Uart1_Rx_Cnt >= 255) //溢出判断
{
Uart1_Rx_Cnt = 0;
memset(RxBuffer,0x00,sizeof(RxBuffer));
HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF);
// TIM2->CR1|=0x01;
}
else
{
RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer; //接收数据转存
if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
{
c=1;
char i;
for(i=0;i<5;i++)
{
if(RxBuffer[i]==senddata[i])
{
}
else
{
c=0;
}
}
if(c)
{
for(i=0;i<4;i++)
{
data[i]= data[i+1];//数据前移
}
num++;
data[4]= 0;//清空数组
if(data[0]==0){//关闭定时器
HAL_TIM_Base_Stop_IT(&htim2);
}
}
while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
Uart1_Rx_Cnt = 0;
memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
}
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再开启接收中断
}
数据处理完了,发送的数据也好了,最后的是超时重发了, 原理为:当我发送完数据后开启定时器,定时器开始计时,当上机接收到数据后把上机收到的数据回发,当我这边检测到数据,产生接收中断后便接收数据,然后对数据进行解校验和与原发送的数据进行对比,最后才进行关闭定时器,还是,不关等待定时器进行溢出然后发送数据。
最后功能便实现了?
实现了当按键按下后串口便发送,且每300ms发送一次,直到返回一样的数据后才暂停发送或者发送下一个按键数据。
这为板子
新人写作,一定有误,望多见谅。请多包涵。全部的代码随后我也发上,请随意取用。
|