收到富芮坤的开发板已经一个多月了,以前玩的蓝牙用的是串口驱动的模块,用的时候只需要通过串口将数据丢给模块即可。第一次玩蓝牙芯片还是蛮有意思的,首先收到的是一个小盒子。
![37a3008e9244bbe09cca3a4431cff6a.jpg 37a3008e9244bbe09cca3a4431cff6a.jpg](https://static.assets-stash.eet-china.com/forum/202006/03/221505tjdtn5tcbzvkjk8d.jpg)
打开盒子能看到的是一块板子,一条数据线和一张资料下载的说明。如果你手上没有专业的下载器,不要慌,可以用提供的数据线下载。
![ac6b85f69e6a862cba055f874fdab28.jpg ac6b85f69e6a862cba055f874fdab28.jpg](https://static.assets-stash.eet-china.com/forum/202006/03/221552i0z01zviqhnly5yx.jpg)
光说不练假把式,上电椅额,,,找把椅子坐好上电。{:2_39:}首先看到的就是富芮坤的图标。至于上面有啥资源,用到再说。(说了你也记不住,记住了也用不到,不说也罢)
![3.jpg 3.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/212815yqpn422jqfk4ggfd.jpg)
看完正面后给他翻个身,能看到显示屏的接口和富芮坤的logo。
![4.jpg 4.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/213236dvri7o87n6769uxv.jpg)
接下来先看看它本身有啥功能吧,这里有的小伙伴说按键按下没反应,其实是需要先将板上的管脚进行短接。将KEY1和KEY2分别短接后按键就能用了,要使用到串口也同样需要分别短接TXD和RXD,后续要用到温湿度传感器的话还得将SDA和SCL分别短接。
![5iic.jpg 5iic.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/213947gyfdzld0wx1a1mac.jpg)
下面介绍一下人机交互界面,左边的是复位按键,中间的是模式切换按键,右边的用于在当前模式下选择不同的内容。
![6.jpg 6.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/214515azs7tbbr9uiq99t2.jpg)
下面看看串口的数据,开机上电后串口会打印出一串数据,至于是啥我也不关心。
![9开机.PNG 9开机.PNG](https://static.assets-stash.eet-china.com/forum/202007/09/214957x091691nn91lxcbs.png)
但是发现个有趣的现象,长按按钮和短按按钮是能被识别到的
![10按键.PNG 10按键.PNG](https://static.assets-stash.eet-china.com/forum/202007/09/215228b7ueiujgn9jci0xn.png)
将模式切换到温湿度模式后(按中间的按钮切换),串口会对应显示温湿度,气压,步数等信息。当然显示屏上也会有对应的显示。
![11温湿度.PNG 11温湿度.PNG](https://static.assets-stash.eet-china.com/forum/202007/09/215648em22yudd3vgqqbvz.png)
![7.jpg 7.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/215716mkvdc19e3dex4m99.jpg)
好了,开完箱看完官方的东西就开始魔改了。首先把初始化图片搞掉!!
要显示图片首先要将图片转为数据,这里可以使用“image2lcd”。安装完成打开后如图9所示。点击右下角的注册输入注册码即可完成注册,如不注册生成的图片会有水印。左下角的参数设置不要搞错。打开导入图片(应为bmp格式),点击保存后保存为.h文件即可。
![1.png 1.png](https://static.assets-stash.eet-china.com/forum/202007/09/220442szf1yf5k56e6he1z.png)
生成数据后数据要放哪里才能显示为图片呢?这是个好问题!!!在你的工程里找bmp.h文件,把里面的数据替换掉就OK了。
![1.png 1.png](https://static.assets-stash.eet-china.com/forum/202007/09/220828i6c04nf473ntq0b6.png)
![1.png 1.png](https://static.assets-stash.eet-china.com/forum/202007/09/220912ba444f0mztmmai60.png)
编译下载后效果如图所示。(因为之前image2lcd没有注册,所以图左上角有水印;注册后如图显示,左上角的水印消失了。)
![1.jpg 1.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/221226im8gnnszmuzkuhk4.jpg)
![2.jpg 2.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/221232jfc3q31rw1ffmmbr.jpg)
图片显示的代码如图14所示。图片的尺寸为240*240,长度len“480*240”为数组的大小115200。
![1.png 1.png](https://static.assets-stash.eet-china.com/forum/202007/09/221619vyhzyhwhsywsyidh.png)
说了半天,蓝牙开发板首要功能当然是和手机的蓝牙连接啦。
修改“simple_gatt_service.c” 中的“sp_gatt_write_cb”函数,在函数里面加一个定义conn_idx。
static void sp_gatt_write_cb(uint8_t *write_buf, uint16_t len, uint16_t att_idx,uint8_t conn_idx){ for (int i = 0; i < len; i++) { co_printf("Write request: len: %d, 0x%x \r\n", len, write_buf[i]); if(len==1) { gatt_ntf_t ntf_att; ntf_att.att_idx=SP_IDX_CHAR4_VALUE; ntf_att.conidx=conn_idx; ntf_att.svc_id=sp_svc_id; uint8_t ShowStringBuff[30] = {0}; sprintf((char *)ShowStringBuff,"Hello %s!",(char *)write_buf); ntf_att.p_data = (uint8_t *)ShowStringBuff; ntf_att.data_len =sizeof(ShowStringBuff); gatt_notification(ntf_att); //LCD_ShowString(10,75,ShowStringBuff,BLACK); co_printf("Write request: 111111 \r\n"); } if (att_idx == SP_IDX_CHAR1_VALUE) memcpy(sp_char1_value, write_buf, len); if (att_idx == SP_IDX_CHAR3_VALUE) memcpy(sp_char3_value, write_buf, len); if (att_idx == SP_IDX_CHAR5_VALUE) memcpy(sp_char5_value, write_buf, len); } uint16_t uuid = BUILD_UINT16( simple_profile_att_table[att_idx].uuid.p_uuid[0], simple_profile_att_table[att_idx].uuid.p_uuid[1] ); if (uuid == GATT_CLIENT_CHAR_CFG_UUID) { co_printf("Notification status changed\r\n"); if (att_idx == SP_IDX_CHAR4_CFG) { sp_char4_ccc[0] = write_buf[0]; sp_char4_ccc[1] = write_buf[1]; co_printf("Char4 ccc: 0x%x 0x%x \r\n", sp_char4_ccc[0], sp_char4_ccc[1]); } } }
复制代码 在调用函数时多传入一个参数p_msg->conn_idx。
static uint16_t sp_gatt_msg_handler(gatt_msg_t *p_msg){ switch(p_msg->msg_evt) { case GATTC_MSG_READ_REQ: sp_gatt_read_cb((uint8_t *)(p_msg->param.msg.p_msg_data), &(p_msg->param.msg.msg_len), p_msg->att_idx); break; case GATTC_MSG_WRITE_REQ: sp_gatt_write_cb((uint8_t*)(p_msg->param.msg.p_msg_data), (p_msg->param.msg.msg_len), p_msg->att_idx,p_msg->conn_idx); break; default: break; } return p_msg->param.msg.msg_len; }
复制代码 手机蓝牙调试器参数设置:
![1.jpg 1.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/222133ykk1ewnlt1xvteze.jpg)
代码看不懂?问题不大,最后附上源码!!
先看看最后的效果,时间的计时使用的是定时器,时间的更新和PWM数值的接收都来自同一个函数sp_gatt_write_cb
![7222a54741c9894b1ad10517d107fc5.jpg 7222a54741c9894b1ad10517d107fc5.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/223432acrihj5yjyx5o3zc.jpg)
![4b864a3194d3b2d0f8fa941ae1ca9e1.jpg 4b864a3194d3b2d0f8fa941ae1ca9e1.jpg](https://static.assets-stash.eet-china.com/forum/202007/09/223439acn0fcf0xkxz1yrx.jpg)
talk is cheep,show me the code!
开发板接受手机端的数据
extern uint16_t sec,min,hor;static void sp_gatt_write_cb(uint8_t *write_buf, uint16_t len, uint16_t att_idx,uint8_t conn_idx) { co_printf("len:%d\r\n",len); for (int i = 0; i < len; i++) { co_printf("Write request: len: %d, 0x%x \r\n", len, write_buf[i]); /*设置时间*/ if(write_buf[0] == 0xa5) { if(write_buf[1] == 0x01) hor = write_buf[2]; else if(write_buf[1] == 0x02) min = write_buf[2]; else if(write_buf[1] == 0x04) sec = write_buf[2]; } /*设置pwm*/ pwm_update(PWM_CHANNEL_1,1000,write_buf[3]); /*发送数据到手机*/ // if(len==1) // { // gatt_ntf_t ntf_att; // ntf_att.att_idx=SP_IDX_CHAR4_VALUE; // ntf_att.conidx=conn_idx; // ntf_att.svc_id=sp_svc_id; // uint8_t ShowStringBuff[30] = {0}; // sprintf((char *)ShowStringBuff,"Hello %s!",(char *)write_buf); // ntf_att.p_data = (uint8_t *)ShowStringBuff; // ntf_att.data_len =sizeof(ShowStringBuff); // gatt_notification(ntf_att); // // //LCD_ShowString(10,75,ShowStringBuff,BLACK); // co_printf("Write request: 111111 \r\n"); // } if (att_idx == SP_IDX_CHAR1_VALUE) memcpy(sp_char1_value, write_buf, len); if (att_idx == SP_IDX_CHAR3_VALUE) memcpy(sp_char3_value, write_buf, len); if (att_idx == SP_IDX_CHAR5_VALUE) memcpy(sp_char5_value, write_buf, len); } uint16_t uuid = BUILD_UINT16( simple_profile_att_table[att_idx].uuid.p_uuid[0], simple_profile_att_table[att_idx].uuid.p_uuid[1] ); if (uuid == GATT_CLIENT_CHAR_CFG_UUID) { co_printf("Notification status changed\r\n"); if (att_idx == SP_IDX_CHAR4_CFG) { sp_char4_ccc[0] = write_buf[0]; sp_char4_ccc[1] = write_buf[1]; co_printf("Char4 ccc: 0x%x 0x%x \r\n", sp_char4_ccc[0], sp_char4_ccc[1]); } } }
复制代码 周期为1s的定时器,这个函数负责时间的定时计数和定时向手机发送温湿度等数据。
uint16_t sec = 0,min = 0,hor = 0;uint8_t ble_ntf_buff[30],ble_ntf_length; void timer_refresh_fun(void *arg) { int32_t temperature, humidity; uint8_t LCD_ShowStringBuff[30]; float capb18_temp =0,capb_airpress=0; int8_t ret=0; /*时间*/ sec++; if(sec>=60) { sec = 0; min++; } if(min>=60) { hor++; } if(hor>=24) { hor = 0; } co_printf("%d:%d:%d\r\n",hor,min,sec); switch (App_Mode) { case PICTURE_UPDATE: break; case SENSOR_DATA: //SHT30数据读取,并在lcd上显示 ret = sht3x_measure_blocking_read(&temperature, &humidity);//Read temperature humidity if (ret == STATUS_OK) { co_printf("temperature = %d,humidity = %d\r\n",temperature,humidity); sprintf((char *)LCD_ShowStringBuff,"SHT30_T= %0.1f ",temperature/1000.0); LCD_ShowString(20,140,LCD_ShowStringBuff,BLACK); sprintf((char *)LCD_ShowStringBuff,"SHT30_H= %0.1f%% ",humidity/1000.0); LCD_ShowString(20,160,LCD_ShowStringBuff,BLACK); /*数据打包*/ startValuePack(ble_ntf_buff); putFloat(temperature /1000.0); putFloat(humidity /1000.0); ble_ntf_length = endValuePack(); /*数据发到手机*/ gatt_ntf_t ntf_att; ntf_att.att_idx=SP_IDX_CHAR4_VALUE; ntf_att.conidx=0; ntf_att.svc_id=73; ntf_att.p_data = ble_ntf_buff; ntf_att.data_len =ble_ntf_length; gatt_notification(ntf_att); } else { co_printf("SHT30 error reading measurement\n"); sprintf((char *)LCD_ShowStringBuff,"SHT30_T= error "); LCD_ShowString(20,140,LCD_ShowStringBuff,BLACK); sprintf((char *)LCD_ShowStringBuff,"SHT30_H= error "); LCD_ShowString(20,160,LCD_ShowStringBuff,BLACK); } //CAPB18数据读取,并在lcd上显示 ret = CAPB18_data_get(&capb18_temp,&capb_airpress); if(ret == true) { sprintf((char*)LCD_ShowStringBuff,"CAPB18_PRS= %7.5f ",capb_airpress); co_printf("%s\r\n",LCD_ShowStringBuff); LCD_ShowString(20,180,LCD_ShowStringBuff,BLACK); sprintf((char*)LCD_ShowStringBuff,"CAPB18_TMP= %7.5f ",capb18_temp); co_printf("%s\r\n",LCD_ShowStringBuff); LCD_ShowString(20,200,LCD_ShowStringBuff,BLACK); } else { co_printf("CAPB18 error reading measurement\n"); sprintf((char*)LCD_ShowStringBuff,"CAPB18_PRS= error "); co_printf("%s\r\n",LCD_ShowStringBuff); LCD_ShowString(20,180,LCD_ShowStringBuff,BLACK); sprintf((char*)LCD_ShowStringBuff,"CAPB18_TMP= error "); co_printf("%s\r\n",LCD_ShowStringBuff); LCD_ShowString(20,200,LCD_ShowStringBuff,BLACK); } //g-sensor读取,并在lcd上显示 co_printf("=skip count=%d\r\n",get_skip_num());//获取跳动次数 sprintf((char*)LCD_ShowStringBuff,"*skip count* = %d",get_skip_num()); co_printf("%s\r\n",LCD_ShowStringBuff); LCD_ShowString(20,120,LCD_ShowStringBuff,BLACK); /*显示时间*/ sprintf((char *)LCD_ShowStringBuff,"Time %d:%d:%d ",hor,min,sec); LCD_ShowString(20,100,LCD_ShowStringBuff,BLACK); break; case SPEAKER_FROM_FLASH://播放flash中的音频demo sprintf((char*)LCD_ShowStringBuff,"Press the key K2 to start the audio"); LCD_ShowString(20,100,LCD_ShowStringBuff,BLACK); if(!Flash_data_state) { //如果flash中没有有效的音频数据,则提示如下 sprintf((char*)LCD_ShowStringBuff,"There is no audio data in flash!"); LCD_ShowString(20,140,LCD_ShowStringBuff,BLACK); } else { LCD_Fill(0,140,240,170,BACK_COLOR);// } break; default: break; } }
复制代码 关于计步功能,官方的历程把手甩断都某得反应,查了半天的资料终于发现了点端倪,main函数里有这么一段代码,把它将0改为1即可。
const struct jump_table_version_t _jump_table_version __attribute__((section("jump_table_3"))) = { .firmware_version = 0x00000001, };
复制代码 关于视频中的风扇调速使用的是PWM,在main函数中使能硬件。(最后两行就是PWM初始化)
void user_entry_before_ble_init(void){ /* set system power supply in BUCK mode */ pmu_set_sys_power_mode(PMU_SYS_POW_BUCK); pmu_enable_irq(PMU_ISR_BIT_ACOK | PMU_ISR_BIT_ACOFF | PMU_ISR_BIT_ONKEY_PO | PMU_ISR_BIT_OTP | PMU_ISR_BIT_LVD | PMU_ISR_BIT_BAT | PMU_ISR_BIT_ONKEY_HIGH); NVIC_EnableIRQ(PMU_IRQn); // Enable UART print. system_set_port_pull(GPIO_PA2, true); system_set_port_mux(GPIO_PORT_A, GPIO_BIT_2, PORTA2_FUNC_UART1_RXD); system_set_port_mux(GPIO_PORT_A, GPIO_BIT_3, PORTA3_FUNC_UART1_TXD); uart_init(UART1, BAUD_RATE_115200); if(__jump_table.system_option & SYSTEM_OPTION_ENABLE_HCI_MODE) { /* use PC4 and PC5 for HCI interface */ system_set_port_pull(GPIO_PA4, true); system_set_port_mux(GPIO_PORT_A, GPIO_BIT_4, PORTA4_FUNC_UART0_RXD); system_set_port_mux(GPIO_PORT_A, GPIO_BIT_5, PORTA5_FUNC_UART0_TXD); } /* used for debug, reserve 3S for j-link once sleep is enabled. */ if(__jump_table.system_option & SYSTEM_OPTION_SLEEP_ENABLE) { co_delay_100us(10000); co_delay_100us(10000); co_delay_100us(10000); } //PWM初始化 system_set_port_mux(GPIO_PORT_D,GPIO_BIT_7, PORTD7_FUNC_PWM1); //system_set_port_mux(GPIO_PORT_D,GPIO_BIT_5, PORTD5_FUNC_PWM5); pwm_init(PWM_CHANNEL_1,1000,99); //pwm_init(PWM_CHANNEL_5,1000,1); }
复制代码 软件的初始化写在simple_peripheral_init中,如代码所示开启PWM就写在了最后一行。
void simple_peripheral_init(void){ // set local device name uint8_t local_name[] = "Simple Peripheral"; gap_set_dev_name(local_name, sizeof(local_name)); // Initialize security related settings. gap_security_param_t param = { .mitm = false, .ble_secure_conn = false, .io_cap = GAP_IO_CAP_NO_INPUT_NO_OUTPUT, .pair_init_mode = GAP_PAIRING_MODE_WAIT_FOR_REQ, .bond_auth = true, .password = 0, }; gap_security_param_init(¶m); gap_set_cb_func(app_gap_evt_cb); gap_bond_manager_init(0x7D000, 0x7E000, 8, true); gap_bond_manager_delete_all(); mac_addr_t addr; gap_address_get(&addr); co_printf("Local BDADDR: 0x%2X%2X%2X%2X%2X%2X\r\n", addr.addr[0], addr.addr[1], addr.addr[2], addr.addr[3], addr.addr[4], addr.addr[5]); // Adding services to database sp_gatt_add_service(); speaker_gatt_add_service(); //创建Speaker profile, //按键初始化 PD6 PC5 pmu_set_pin_pull(GPIO_PORT_D, (1<<GPIO_BIT_6), true); pmu_set_pin_pull(GPIO_PORT_C, (1<<GPIO_BIT_5), true); pmu_port_wakeup_func_set(GPIO_PD6|GPIO_PC5); button_init(GPIO_PD6|GPIO_PC5); demo_LCD_APP(); //显示屏 demo_CAPB18_APP(); //气压计 demo_SHT3x_APP(); //温湿度 gyro_dev_init(); //加速度传感器 //OS Timer os_timer_init(&timer_refresh,timer_refresh_fun,NULL);//创建一个周期性1s定时的系统定时器 os_timer_start(&timer_refresh,1000,1); pwm_start(PWM_CHANNEL_1); }
复制代码 可能眼尖的就发现第二个界面的“富芮坤”怎么变了?咋整的?打开lcd.c文件能看到有一个lcd_show_logo函数,LCD_ShowChinese函数就是现实中文的函数。前两个参数是要显示的位置,第三个参数为数据所在数组的位置,第四个参数为中文的大小(其实就是选择不同的数组),最后一个为字体颜色。
void lcd_show_logo(const uint8_t* mode_str){ uint8_t LCD_ShowStringBuff[30] = {0}; BACK_COLOR=WHITE; LCD_Clear(WHITE); LCD_ShowChinese(10+TITLE_OFFSET,20,6,32,BLUE); //谭 LCD_ShowChinese(85+TITLE_OFFSET,20,7,32,BLUE); //伟 LCD_ShowChinese(160+TITLE_OFFSET,20,8,32,BLUE); //志 sprintf((char *)LCD_ShowStringBuff,"Mode:"); LCD_ShowString(5,75,LCD_ShowStringBuff,BLACK); LCD_ShowString(50, 75, mode_str, BLACK); }
复制代码 应该没有漏掉什么要点了,那么就附上源码吧。打开代码请进入以下路径FR801xH-SDK\examples\dev1.0\ble_simple_peripheral\keil(最近在学github,之后会丢过去的)
学习心得:
![](static/image/filetype/pdf.gif)