备赛之前,区区有学过I2C和串口相关的教程,这里不对原理展开具体的讲解,可以参考火哥的教程。
I2C
将上一个博客的例程复制命名为Ag_05,比赛的时候给参赛选手的数据包里会有I2C的文件,所以我们这里直接将底层驱动代码参考文件夹中的i2c_hal.c和i2c_hal.h两个文件复制到bsp文件夹中。
打开.ioc文件,根据数据手册配置PB6和PB7为Output。点击生成代码,打开工程文件。
把i2c_hal.c添加到工程。可以看到已经封装好了很多i2c的函数,我们只需要去调用完成相应功能就行。这里我们假定要将之前例程中得到的频率值存储到EEPROM中。在i2c_hal.c中加入:
void EEP_Write(uchar addr,ucahr dat) //写函数
{
I2CStart(); //调用上面封装好的函数开始I2C通讯
I2CSendByte(0xa0); //根据芯片手册发送指令,接下来要写操作
I2CWaitAck(); //发送了数据,要等待
I2CSendByte(addr); //发送要写入的地址
I2CWaitAck(); //等待
I2CSendByte(dat); //发送要写入的数据
I2CWaitAck(); //等待
I2CStop(); //结束I2c通讯
}
uchar EEP_Read(uchar addr) //读函数
{
uchar dat; //定义一个变量来储存读取的数据
I2CStart(); //开始通讯
I2CSendByte(0xa0); //根据芯片手册发送写入指令,因为要先发送地址数据写入到EEPROM
I2CWaitAck(); //等待
I2CSendByte(addr); //发送要读取的地址
I2CWaitAck();
I2CStop();
I2CStart();
I2CSendByte(0xa1); //发送读取指令
I2CWaitAck(); //等待
dat = I2CReceiveByte(); //将对应地址的数据接收到这个变量
I2CWaitAck(); //等待
I2CStop();
return dat;
}
注意,写命令和读命令是根据芯片手册得到的。
现在,我们有了写数据和读数据两个函数,将之在.h文件中声明后,在main.c中实现相关功能即可。例如,我们按下B3按键,将频率1存入EEPROM。在按键函数key_pro(void)中加入
if(key_state[2].short_flag==1) //判断如果为按下B3
{
uchar frq_h; //因为frq_1是16位数据类型,而定义的写,读函数的形参都是8位
uchar frq_l; //所以要用两个变量分别去存高8位和低8位数据
frq_h = frq_1 >> 8;
frq_l = frq_1 & 0xff;
EEP_Write(1,frq_h); //在地址为1的空间写入高八位数据
I2CWaitAck(); //等待
EEP_Write(2,frq_l); //在地址为2的空间写入低八位
key_state[1].short_flag=0;
}
在显示函数dis_pro()中加入:
uint eep_dat = (EEP_Read(1)<<8)+EEP_Read(2); //定义一个变量来存读取的数据
sprintf(dis_2," EEP_I2C:%d",eep_dat);
LCD_DisplayStringLine(Line3,(unsigned char *)dis_2);
编译无误,下载完成
串口通信
根据题目要求,我们需要开发板通过串口通信接受一个字符串,按要求处理数据并打印到LCD屏上,如果接受出错则打印Error。
在cube中打开并配置USART1相关参数。将模式设置为异步通信,波特率设置为9600(根据本例程参考的题目所给要求),使能中断然后生成代码。
我们这里还是用中断的方式,先在interrupt.c文件中编写回调函数,当通过串口接受到数据,先将数据存储到预先定义好的数组中以待后面显示。
char Re_Dat[30]; //用来存储接受到的字符串
uint8_t rxdat; //用来存当前接受到的单个的字符
uint8_t re_pointer; //用来指向数组存储位置的光标
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //串口接受中断回调函数
{
Re_Dat[re_pointer++] = rxdat; //将缓存的数据放入数组,光标往后移动一位
HAL_UART_Receive_IT(&huart1,&rxdat,1); //开启中断,为接受下一个数据作准备。rxdat缓存接收到的数据,1为接受数据进入中断的阈值。
}
然后,将上面定义的变量和数组在main.c中作外部调用。首先用HAL_UART_Receive_IT(&huart1,&rxdat,1);
打开串口接受中断定义一个处理接收到的数据的函数,这里按照题目要求来编写,如要将22位的字符串分为4、4、12的格式在LCD中显示。
char car_type[5]; //将22位字符串分为三个部分分别存储
char car_time[13];
char car_dat[5];
void Re_Usart_Pro() //定义处理接收到的数据的函数
{
if(re_pointer>0)
{
if(re_pointer==22)
{
sscanf(Re_Dat,"%4s:%4s:%12s",car_type,car_dat,car_time); //将接受到的数据按照既定格式分别放入三个数组中
}
else
{
char temp[20];
sprintf(temp,"Error!"); //如果接受的数据不够则打印Error
HAL_UART_Transmit(&huart1,(uint8_t *)temp,strlen(temp),50); //库函数发送,最后一个是发送的最大时间,如果超过就退出发送,注意要设置大一些
}
re_pointer=0; //将光标返回置开头
memset(Re_Dat,0,30); //将接受数据的数组全部置零
}
}
接下来只需要在显示函数dis_pro中将接受的数据进行显示即可,这里可以设置一个变量view来表示多个界面,将之前所有例程的显示用0来表示,这里的串口通信数据用1来表示。
dis_pro:
void dis_pro(void)
{
if(view == 0)
{
char dis[20]; //定义一个内存为20的数组
char dis_1[20];
char dis_2[20];
sprintf(dis," PWM:"); //将字符串传到指定数组中
LCD_DisplayStringLine(Line1,(unsigned char *)dis); //在第一行显示
sprintf(dis_1," PWM:%d",frq_1);
LCD_DisplayStringLine(Line5,(unsigned char *)dis_1);
sprintf(dis_2," PWM:%d",frq_2);
LCD_DisplayStringLine(Line6,(unsigned char *)dis_2);
sprintf(dis_2," V:%.2f",Get_Adc(&hadc1));
LCD_DisplayStringLine(Line8,(unsigned char *)dis_2);
sprintf(dis_2," V:%.2f",Get_Adc(&hadc2));
LCD_DisplayStringLine(Line9,(unsigned char *)dis_2);
uint eep_dat = (EEP_Read(3)<<8)+EEP_Read(4); //定义一个变量来存读取的数据
sprintf(dis_2," EEP_I2C:%d",eep_dat);
LCD_DisplayStringLine(Line3,(unsigned char *)dis_2);
}
//上面的代码在之前例程有,这里只是连贯起来
if(view == 1)
{
char arr[30];
sprintf(arr," UART:");
LCD_DisplayStringLine(Line1,(unsigned char *)arr);
sprintf(arr,"car_type:%s",car_type);
LCD_DisplayStringLine(Line4,(unsigned char *)arr);
sprintf(arr,"car_dat:%s",car_dat);
LCD_DisplayStringLine(Line5,(unsigned char *)arr);
sprintf(arr,"car_time:%s",car_time);
LCD_DisplayStringLine(Line6,(unsigned char *)arr);
}
}
在按键函数key_pro中,将按键B4设置为界面切换按钮。在函数中加入:
if(key_state[3].short_flag == 1)
{
view++;
if(view == 2)
view = 0;
LCD_Clear(Black); //清屏
key_state[3].short_flag = 0;
}
将Re_Usart_Pro()
函数放入主函数while中,编译无误,下载完成。