-
GPIOGPIO API介绍wifiiot_gpio.h接口中包含声明GPIO接口函数,这些功能用于初始化GPIO。通过控制GPIO输出的高低电平信号来实现LED灯的闪烁。代码首先要使用GPIO的功能,则要引入两个头文件#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"初始化GPIO设置GPIO为复用,例子中设置为普通GPIO在一个参数填的是引脚号(下面红字)IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);设置引脚为输出模式例子中要输出高低电平控制LED灯闪烁,因此用“OUT”依靠“while”循环重复引脚的高低电平变换,实现LED的闪烁拓展实验实现GPIO通过打印将引脚方向的读取,以及高低电平的输出显示出来GPIO中断GPIO中断API介绍wifiiot_gpio.h中包含声明GPIO中断相关函数通过检测引脚中传出的中断信号来判断按键的状态代码与上讲相同,初始化LED灯设置输出方向为OUT与上讲不同的是,接下来初始两个按键的代码同样赋值引脚为GPIO由于是检测输出的高低电平来判断按键状态,所以按键设置为输入状态设置按键在不按下的状态时,LED灯一直处于高电平的状态中因此当按键按下时LED灯为低电平状态GPIO引脚的电平发生跳变,即认定为一次按下按键的动作通过设置GPIO中断API,设置功能拓展实验PWMPWM API介绍wifiiot_pwm.h中包含声明PWM接口函数通过引脚输出的PWM波控制LED灯闪烁的亮度代码初始化GPIO将引脚复用功能设置为PWM模式因为PWM波要为输出状态,所以设置引脚为输出初始化PWM波利用while循环控制LED灯的亮度变化拓展实验ADCADC API介绍wifiiot_adc.h中包含声明ADC接口函数利用F1按键模拟电压的变化即读取ADC引脚接口的电压值并输出显示代码首先上拉了ADC对应GPIO引脚,使引脚一直处于高电平的状态再利用whlie循环读取电压值并显示打印读取电压值用的是AdcRead()接口函数可以修改取得次数获得平均值,例子中获取了8次求平均值I2CI2C API介绍wifiiot _i2c.h中包含声明I2C接口函数wifiiot_i2c_ex.h中包含声明扩展I2C接口函数NFC芯片接在两个GPIO引脚上因此利用编写程序让GPIO引脚产生I2C信号控制NFC芯片代码改编写程序实现了NFC芯片的写数据的操作,即将数据写入初始化GPIO将两个GPIO接口复用功能设置为I2C初始化I2C接口设置I2C频率用于中途修改频率,防止再次进行初始化操作调用I2CD 驱动,来实现NFC的写操作其实就是调用了读写的操作,以下为三数据的编写UARTUART API介绍wifiiot_uart.h中包含声明UART接口函数利用UART对应的两个引脚(GPIO5,GPIO6)对UART数据进行收代码调用UartInit进行初始化编写程序中配置的波特率要和接入的UART上的波特率相同初始化之后进行数据的收发UartWrite进行发数据UartRead进行收数据实现自发自收的功能通过打印将收发的数据显示出来
-
GPIOGPIO API介绍wifiiot_gpio.h接口中包含声明GPIO接口函数,这些功能用于初始化GPIO。通过控制GPIO输出的高低电平信号来实现LED灯的闪烁。代码首先要使用GPIO的功能,则要引入两个头文件#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"初始化GPIO设置GPIO为复用,例子中设置为普通GPIO在一个参数填的是引脚号(下面红字)IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);设置引脚为输出模式例子中要输出高低电平控制LED灯闪烁,因此用“OUT”依靠“while”循环重复引脚的高低电平变换,实现LED的闪烁拓展实验实现GPIO通过打印将引脚方向的读取,以及高低电平的输出显示出来GPIO中断GPIO中断API介绍wifiiot_gpio.h中包含声明GPIO中断相关函数通过检测引脚中传出的中断信号来判断按键的状态代码与上讲相同,初始化LED灯设置输出方向为OUT与上讲不同的是,接下来初始两个按键的代码同样赋值引脚为GPIO由于是检测输出的高低电平来判断按键状态,所以按键设置为输入状态设置按键在不按下的状态时,LED灯一直处于高电平的状态中因此当按键按下时LED灯为低电平状态GPIO引脚的电平发生跳变,即认定为一次按下按键的动作通过设置GPIO中断API,设置功能拓展实验PWMPWM API介绍wifiiot_pwm.h中包含声明PWM接口函数通过引脚输出的PWM波控制LED灯闪烁的亮度代码初始化GPIO将引脚复用功能设置为PWM模式因为PWM波要为输出状态,所以设置引脚为输出初始化PWM波利用while循环控制LED灯的亮度变化拓展实验ADCADC API介绍wifiiot_adc.h中包含声明ADC接口函数利用F1按键模拟电压的变化即读取ADC引脚接口的电压值并输出显示代码首先上拉了ADC对应GPIO引脚,使引脚一直处于高电平的状态再利用whlie循环读取电压值并显示打印读取电压值用的是AdcRead()接口函数可以修改取得次数获得平均值,例子中获取了8次求平均值I2CI2C API介绍wifiiot _i2c.h中包含声明I2C接口函数wifiiot_i2c_ex.h中包含声明扩展I2C接口函数NFC芯片接在两个GPIO引脚上因此利用编写程序让GPIO引脚产生I2C信号控制NFC芯片代码改编写程序实现了NFC芯片的写数据的操作,即将数据写入初始化GPIO将两个GPIO接口复用功能设置为I2C初始化I2C接口设置I2C频率用于中途修改频率,防止再次进行初始化操作调用I2CD 驱动,来实现NFC的写操作其实就是调用了读写的操作,以下为三数据的编写UARTUART API介绍wifiiot_uart.h中包含声明UART接口函数利用UART对应的两个引脚(GPIO5,GPIO6)对UART数据进行收代码调用UartInit进行初始化编写程序中配置的波特率要和接入的UART上的波特率相同初始化之后进行数据的收发UartWrite进行发数据UartRead进行收数据实现自发自收的功能通过打印将收发的数据显示出来
-
1.赛题及硬件方案分析: 刚得到消息,我们队被推荐为国奖,国一国二还没定,大概率是国二,因为测评时发挥部分的小车二在转弯时因为电池原因急转弯失败,发挥部分就没有成功验收。刚收到消息就更新了一下,本方案的成本可以说是此赛题方案中成本最低的。 通过题目可以很容易的看出来,这道题有最难的两个点:巡线+数字识别。 此题的巡线又不同于以往电赛题目的黑白巡线,此题是巡红白线。这样就会带来一个问题,以往巡黑白线我们最常用的是红外传感器,但是此题中的红线和白色背景两者间的吸光能力差别很小,一般的红外传感器根本难以分辨,这就需要用到较为不常用的灰度传感器或者是颜色传感器。据我所知,开赛前就已经准备好这两种传感器的小组还是较少的,包括我们组就没有准备,只能用摄像头代替,用对应的算法进行巡线。 数字识别用到的硬件就没什么好说的了,肯定用到摄像头,我们组用的是赛前已经准备好的OpenMV,OpenMV上手还是很快的,但是需要有一定的Python基础 。在比赛过程中也有很多大佬认为OpenMV同时进行巡线和数字识别的话,其帧率会很低,我们组当然也遇到了以上问题,具体的解决方法在后面会讲到。 另外,题目中要求检测到200g药品的装载和卸载,一般的话可以用压力传感器,但为了方便我们直接用的红外对管,以检测障碍物的方式判断药品是否装载。2.用到的主要器件清单: STM32F407ZGT6单片机 OpenMV4智能处理摄像头 舵机转向四驱车 锂电池、5V、12V降压模块 L298N电机驱动模块 红外传感器 蓝牙模块HC-05 LED指示灯 以上器件都是一辆小车用到的,如果要做发挥部分的话就需要两辆车了,不过小车2用到的器件基本不变。我们采取的方案是一辆小车用一个摄像头,我看网上有的大佬一辆小车左右两边各有一个摄像头,这样的话写代码就会较为简便,不过一般的OpenMV价格在300到500之间,除非你的经费充足且赛前已经准备好,否则还是老老实实用一个摄像头肝吧.....就像我们组,当时只有一个OpenMV,第二个还是临时找关系借的。3.各部分思路及代码实现 (1).小车舵机、马达驱动 这一部分的代码就不贴上了,都是基本的PWM输出,可以参照正点原子的相关例程,并且两辆小车的代码都一样,只不过舵机让车轮保持直行时的PWM脉宽不同,到时候自己找到并在代码中改数字即可。 (2).蓝牙通信 蓝牙通信的代码说白了就是串口通信,代码如下: #include "stm32f4xx.h"#include "bluetooth.h"/*蓝牙通信:USART3,所用引脚为PC10,PC11*/void bluetooth_U3_Init(){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //使能GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //使能USART2时钟//串口1对应引脚复用映射GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_USART3); //GPIOA9复用为USART1GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_USART3); //GPIOA10复用为USART1//USART1端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //GPIOA9与GPIOA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //上拉GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化PA9,PA10//USART1 初始化设置USART_InitStructure.USART_BaudRate = 9600;//波特率设置USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式USART_Init(USART3, &USART_InitStructure);//初始化串口1USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //开启相关中断//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; //串口1中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、USART_Cmd(USART3, ENABLE); //使能串口1}/* 蓝牙使能引脚:PA9 */void bluetooth_IO_Init(){GPIO_InitTypeDef GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //??GPIOA??GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //??50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //??????GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //??GPIO_Init(GPIOC, &GPIO_InitStructure); //???PA9,PA10}注意:以上代码中蓝牙使能引脚PA9可以不进行初始化,这样就会一直使能,如果要进行初始化的话需要设置为开漏输出 。 (3).单片机与OpenMV的串口通信 此代码与蓝牙通信代码一样,都是串口通信,只不过我用的是USART5,所用引脚为PC12,PD2 (4).单片机与OpenMV的通信协议 因为OpenMV只能通过串口传输字符(除非使用OpenMV的数据流打包,但是我们传输的一般一次只有一个字符,所以不用数据流打包),比如,你要把 52 这个数传给单片机,OpenMV会自动把 52 这个数转换为两个字符'5'和‘2’传输,所以我们要在单片机和OpenMV之间定义一个统一的通信协议,代码如下(此代码中OpenMV传输的整数的范围在-99到99之间):/* openmv发送过来的字符串转换为数据:帧头为' ',帧尾为'.',转换后只保留整数,中间用小数点判断,函数返回值为0则数据格式出错*/signed char CharCon(signed char c[],signed char x){int i = 0;int j = 0;while(c[i] != '.') {c[i] = c[i] - '0';i++;}j = i;if(c[0] == ('-'-'0')){i = 4;c[0] = 0;}switch(i){case 1:x = c[i-1];break;case 2:x = c[i-1] + 10*c[i-2];break;case 3:x = -(c[i-1] + 10*c[i-2]);break;case 4:x = -(c[j-1] + 10*c[j-2]);break;default:return 0;break;} return x;} OpenMV发送的数据格式:uart.write(" ")uart.write(str(int(deflection_angle)))uart.write(".") (5).单片机main文件中的函数: main函数中需要用到状态机的编程逻辑,在单片机收到OpenMV发送的数据时需要判断接收到的是数据帧,还是命令,比如收到的是'f',则接收到的是笔直向前的命令。// PB10是红外对管的输入引脚#include "stm32f4xx.h"#include "sys.h"#include "delay.h"#include "duoji.h"#include "dianji.h"#include "bluetooth.h"#include "to_openmv.h"#include "hongwai.h"#include "usart.h"#define Center 88 //小车前轮保持居中不偏时单片机输出的PWN脉宽#define Hongwai_IO PBin(10) //装载完药品,hongwai_IO = 0#define Red PAout(14)#define Green PEout(5)#define White PAout(13)//以下定义的是状态机的各种状态,在最终的代码中有些并没有用到#define Start 0 //初始的等待状态#define Turn_left 1#define Turn_right 2#define Turn_no 3#define Stop 4#define Wait 5#define Slower 6#define Faster 7#define Forward 8#define Backward 9#define Back 10#define Change 11#define Stoping 12#define Changezuo 13#define Changeyou 14#define Fuwei 15#define Finish 16#define Nfuwei 17#define Zanwait 18int t = 0;int speed = 200; //马达速度signed char angle = 0; //巡线时舵机转的角度signed char change_angle = 30; //路口停下来识别数字时微调的角度uint16_t bluetooth = 0; //接收到的蓝牙消息uint16_t temp = 0;char state = 0; //状态机中的当前状态char flag = 0; //是否已经识别到要去的病房号signed char rec_flag = 0; //单片机与OpenMV通信中的命令标志位signed char openmv_rec[4] = {0}; //OpenMV发送的数据signed char i = 0;signed char task = 0; //当前执行的是任务几/* 指示灯引脚初始化 */void LED_Init(){ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_InitTypeDef GPIO_Instruct; GPIO_Instruct.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14; GPIO_Instruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_Instruct.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Instruct.GPIO_OType = GPIO_OType_PP; GPIO_Instruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_Instruct); GPIO_Instruct.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOE, &GPIO_Instruct);}void All_Init(){ LED_Init(); hongwai_IO_Init(); dianji_IO_Init(); dianji_PWM_Init(1000 - 1, 84 - 1); duoji_PWM_Init(2000 - 1, 840 - 1); //0 bluetooth_U3_Init(); //bluetooth_IO_Init(); openmv_U5_Init(); // computer_U1_Init(); uart_init(9600); delay_init(168);}void turn_r(int t){ TIM_SetCompare1(TIM3, 88); PFout(5) = 0; PFout(7) = 1; PCout(4) = 1; PCout(5) = 0; TIM_SetCompare1(TIM5, t); TIM_SetCompare1(TIM13, t); delay_ms(3000);}int main(void){ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); All_Init(); while (1) { switch (state) { case Start: turn(0); while (flag == 0); //已经识别到病房号 Green = 0; White = 1; delay_ms(1000); White = 0; while (flag == 1) { if (Hongwai_IO == 0) { delay_ms(1000); if (Hongwai_IO == 0) { state = Forward; break; } } } break; case Forward: turn(angle); zhengzhuan(speed); break; case Turn_right: zhengzhuan(speed); turn_right(); delay_ms(400); break; //假设急转弯1.5S已经转过去,具体参数需要调试 case Turn_left: turn_left(); zhengzhuan(200); delay_ms(400); break; //假设急转弯1.5S已经转过去,具体参数需要调试 case Stop: stop(); delay_ms(20); fanzhuan(speed); delay_ms(300); stop(); turn(0); state = Stoping; break; case Wait: stop(); turn(0); Red = 1; if (Hongwai_IO == 1) { delay_ms(500); if (Hongwai_IO == 1) { USART_SendData(UART5, 's'); Red = 0; state = Back; flag = 0; } } break; case Zanwait: stop(); turn(0); Red = 1; if (Hongwai_IO == 1) { delay_ms(500); if (Hongwai_IO == 1) { USART_SendData(USART3, 'o'); delay_ms(10); USART_SendData(UART5, 's'); Red = 0; state = Back; flag = 0; } } break; case Back: fanzhuan(speed - 50); // turn(-angle); delay_ms(400); state = Backward; USART_SendData(UART5, 's'); break; case Backward: turn(3 - angle); fanzhuan(speed - 50); break; case Change: turn(angle); zhengzhuan(speed); state = Stop; delay_ms(500); break; case Changezuo: fanzhuan(speed); delay_ms(400); stop(); turn(30); zhengzhuan(speed); change_angle = 30; delay_ms(500); state = Stoping; USART_SendData(UART5, 's'); break; case Changeyou: fanzhuan(speed); delay_ms(400); stop(); turn(-30); zhengzhuan(speed); change_angle = -30; delay_ms(500); state = Stoping; USART_SendData(UART5, 's'); break; case Fuwei: turn(change_angle); fanzhuan(speed); delay_ms(500); stop(); turn(0); zhengzhuan(speed); delay_ms(400); stop(); break; case Nfuwei: turn(change_angle); fanzhuan(speed); delay_ms(500); stop(); turn(0); break; case Stoping: stop(); turn(0); break; case Finish: stop(); turn(0); Green = 1; flag = 0; state = Start; break; default: break; } }}/* 蓝牙接收 */void USART3_IRQHandler(){ if (USART_GetITStatus(USART3, USART_IT_RXNE)) { bluetooth = USART_ReceiveData(USART3); } USART_ClearITPendingBit(USART3, USART_IT_RXNE);}/* openmv接收 */void UART5_IRQHandler(){ if (USART_GetITStatus(UART5, USART_IT_RXNE)) { char j = 0; signed char temp = USART_ReceiveData(UART5); if (temp == ' ') { rec_flag = 1; i = 0; } else if (rec_flag == 1) { if (temp == '.') { openmv_rec[i] = '.'; angle = CharCon(openmv_rec, angle); rec_flag = 0; for (j = 0; j < 4; j++) openmv_rec[j] = 0; } else { openmv_rec[i] = temp; i++; } } else //rec_flag == 0,接收的为指令 { switch (temp) { case 's': state = Stop; break; case 'l': state = Turn_left; break; case 'r': state = Turn_right; break; case 'f': state = Forward; break; case 'z': state = Wait; break; case 'b': state = Backward; break; case 'd': state = Finish; break; case 'e': state = Zanwait; break; case 'o': flag = 1; break; case 'c': state = Change; break; case 'x': state = Changezuo; break; case 'y': state = Changeyou; break; case 'w': state = Fuwei; break; case 'n': state = Nfuwei; break; case 't': state = Stoping; break; default: break; } } } USART_ClearITPendingBit(UART5, USART_IT_RXNE);} 一下部分是OpenMV部分的代码,包括巡线和数字识别的算法,以及赛题各任务的判断逻辑 (6).巡线 思路:将摄像头一帧图片的上半部分划分为三个平行的感兴趣区,在三个感兴趣区中分别寻找最大的红色色块,获得三个中心坐标,然后给予其不同的权重后计算平均质心坐标,用此质心坐标计算得到巡线时的偏转角度。 为什么只将一帧图片的上半部分?因为下半部分容易受到小车的阻挡或者阴影干扰 RED_THRESHOLD = [(200,10,10),(255,50,50)]#色块阈值BLACK_THRESHOLD = [(0, 48, -58, 49, -36, -1)]ROIS = [(0, 15, 160, 25, 0.9), (0, 40, 160, 25, 0.6), (0, 65, 160, 30, 0.6)]#--------------------------------------- 判断直线角度 ------------------------------------------------ centroid_sum = 0 dx = 0 for r in ROIS: blobs = img.find_blobs(RED_THRESHOLD, roi=r[0:4], merge=True) # r[0:4] is roi tuple. if blobs: # Find the blob with the most pixels. largest_blob = max(blobs, key=lambda b: b.pixels()) # 返回最大值像素点 #img.draw_rectangle(largest_blob.rect()) # 将此区域的像素数最大的颜色块画矩形和十字形标记出来 #img.draw_cross(largest_blob.cx(),largest_blob.cy()) #print(largest_blob.cx()) centroid_sum += (80-largest_blob.cx())*r[4] # r[4] is the roi weight. weight_sum = weight_sum + r[4] #计算centroid_sum,centroid_sum等于每个区域的最大颜色块的中心点的x坐标值乘本区域的权值 if weight_sum: center_pos = (centroid_sum / weight_sum) weight_sum = 0#--------------------------------------- 什么也识别不到则认为是到达了虚线 --------------------------------#----------------------------------------- 求解直线角度----------------------------------------------- deflection_angle = 0 ns = center_pos/60 # QQVGA 160x120. deflection_angle = -math.atan(ns) deflection_angle = math.degrees(deflection_angle) deflection_angle = 0 - int(deflection_angle) #print(str(deflection_angle))#------------------------------------------ 直行 ---------------------------------------------------- uart.write(" ") uart.write(str(int(deflection_angle))) uart.write(".") (7).识别十字路口 我们需要识别十字路口,然后停车进行数字识别或者进行其他操作。思路:感兴趣区域取左上角和右上角,当两个区域同时检测到红色色块时,就表示识别到了十字路口。ROI = [(10,0,40,50),(110,0,50,50)] blobs1 = img.find_blobs(RED_THRESHOLD, roi=ROI[0],area_threshold=150, merge=True) if blobs1: #img.draw_rectangle(blobs1.arect()) blobs2 = img.find_blobs(RED_THRESHOLD, roi=ROI[1], area_threshold=150,merge=True) if blobs2:#表示已经识别到十字路口 #img.draw_rectangle(blobs2.rect()) print("+") (8).数字识别 一开始我们组用的是特征点检测的方法,写好代码以后发现效果并不好,不仅速度慢而且识别准确率很低,后来就改成了模板匹配的方法,这种方法唯一的缺点就是要想识别准确率高就需要截取较多的模板,不仅有多种大小,还要有多种角度,我们小组最终截取了将近200张模板,如果逐一进行匹配的话速度会非常的慢,但是根据任务要求我们可以灵活匹配,比如基础任务一我们只需要匹配数字一和二的模板,其他任务只有在一开始识别病房号时需要从3到8逐一匹配,但识别到病房号以后,在路口进行数字识别时只需要对病房号的模板进行匹配并判断其在小车的左侧还是右侧,以此来判断应该做转还是右转。核心代码如下:#------------------------------------ 初始时刻识别病房号 ---------------------------------------------- while(wait == 0): if(task == 0): img = sensor.snapshot().lens_corr(strength = 1.6, zoom = 1.0) img=img.to_grayscale() #histogram = img.get_histogram() #Thresholds = histogram.get_threshold() #v = Thresholds.value() img.binary([(0,v)]) for t in templates12: template = image.Image(t) #对每个模板遍历进行模板匹配 r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) if r: #img.draw_rectangle(r, color=255) print(t[10]) #打印模板名字 number=int(t[10]) uart.write("o") wait = 1 break else:#初始时刻任务二三识别病房号 img = sensor.snapshot().lens_corr(strength = 1.6, zoom = 1.0) img=img.to_grayscale() img.binary([(0,v)]) for t in templates3: template = image.Image(t) r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates4: template = image.Image(t) r = img.find_template(template, 0.55,step=4, search=SEARCH_EX) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates5: template = image.Image(t) r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates6: template = image.Image(t) r = img.find_template(template, 0.61,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates7: template = image.Image(t) r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates8: template = image.Image(t) r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 if (max(array1) > 1): m = array1[0] for index in range(0,8): if array1[index] > m: m = array1[index] number = index + 1 print(str(number)) uart.write("o") wait = 1 num = task if(num >2): num = num - 2 continue else:#没识别出来则重新循环 print("no") continue#--------------------------------------- 十字路口前识别数字-------------------------------------------- while(wait == 2): if((task == 2)and(last != 3)): if(n1<2): for loop in range(0,3): img = sensor.snapshot() img.lens_corr(1.6) img=img.to_grayscale() img.binary([(0,v)]) if number==3: for t in template3: template = image.Image(t) r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: #img.draw_rectangle(r,color = 255) print(str(number)) n1=n1+1 if r[0]<60: zuo=zuo+1 else: you=you+1 if number==5: for t in template5: template = image.Image(t) r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: #img.draw_rectangle(r,color = 255) print(str(number)) n1=n1+1 if r[0]<60: zuo=zuo+1 else: you=you+1 原文链接:https://blog.csdn.net/qq_45204725/article/details/121375031
-
GPIO操作GPIO api介绍GPIO引脚查看ADC采样开发ADC的API介绍wifiiot_adc.h接口简介ADC接口函数参数查看ADC对应的GPIO引脚使用用户按键1来模拟GPIO电压变化,GPIO_11对应的是ADC Channel 5,需要编写软件去读取ADC Channel 5的电压按键按下时是低电压,大约在0V左右ADC读取GPIO的电压值打开B4工程的adc_example.c文件,即可查看读取ADC通道5的电压值的代码*读取电压值用的是AdcRead,第一个参数要用adc的第五个通道I2C总线开发I2C的API介绍wifiiot_i2c.h声明接口函数wiffiot_i2c_ex.h包含扩展I2C接口函数查看NFC的I2C对应的GPIO引脚NFC芯片的I2C对应的GPIO引脚是,GPIO0和GPIO1,需要编写软件使用GPIO_0和GPIO_1产生I2C信号去控制NFC芯片I2C读写NFC芯片打开B5工程的i2c_example.c文件,即可查看代码可以实现NFC芯片的写操作*I2C协议的写操作UART读写开发API介绍查看对应GPIO引脚UART1对应的GPIO引脚为GPIO5和GPIO6
-
## ADC采样开发 ### ADC的API介绍 wifiiot_adc.h接口简介 ADC接口函数 ![屏幕截图 2022-07-22 154224.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658479663040215506.png) 参数 ![屏幕截图 2022-07-22 154532.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658479912204896606.png) ### 查看ADC对应的GPIO引脚 ![屏幕截图 2022-07-22 154630.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480064236968018.png) 使用用户按键1来模拟GPIO电压变化,GPIO_11对应的是ADC Channel 5,需要编写软件去读取ADC Channel 5的电压 按键按下时是低电压,大约在0V左右 ### ADC读取GPIO的电压值 打开B4工程的adc_example.c文件,即可查看读取ADC通道5的电压值的代码 *读取电压值用的是AdcRead,第一个参数要用adc的第五个通道 ## I2C总线开发 ### I2C的API介绍 wifiiot_i2c.h声明接口函数 ![屏幕截图 2022-07-22 160248.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480087428143307.png) wiffiot_i2c_ex.h包含扩展I2C接口函数 ![屏幕截图 2022-07-22 160403.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480101622340307.png) ### 查看NFC的I2C对应的GPIO引脚 ![屏幕截图 2022-07-22 160639.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480114101284406.png) NFC芯片的I2C对应的GPIO引脚是,GPIO0和GPIO1,需要编写软件使用GPIO_0和GPIO_1产生I2C信号去控制NFC芯片 ### I2C读写NFC芯片 打开B5工程的i2c_example.c文件,即可查看代码 ![屏幕截图 2022-07-22 161128.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480129803296005.png) 可以实现NFC芯片的写操作 *I2C协议的写操作 ![屏幕截图 2022-07-22 161717.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480140977450636.png) ## UART读写开发 ### UART的API介绍 wifiiot_uart.h包含UART接口函数 ![屏幕截图 2022-07-22 162723.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480153520548667.png) 查看UART1对应的GPIO引脚 ![屏幕截图 2022-07-22 162746.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480163555947784.png) UART1对应的GPIO引脚是GPIO5和GPIO6 ### UART读写数据 打开B6工程下的uart_example.c文件,即可查看代码 *将RXD和TXD短接在一起,实现自接自收 ![屏幕截图 2022-07-22 163930.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658480176334897918.png)
-
# 驱动子系统开发 ## 1.驱动GPIO (1)具体内容 ・GPIO相关API ・如何操作GPIO点亮LED灯 ・如何读取GPIO电平状态 (2)GPIO API介绍 wifiot_gpio.h接口简介: 注意: 这个.h中包含声明GPIO接口函数,这些功能用于初始化GPIO。 常用接口及其常用功能 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658399591179340484.png) (3)与LED对应的GPIO引脚 LED对应的GPIO引脚是GPIO2通过控制GPIO2输出的的电平信号来实现LED灯的闪烁。 ·高电平时点亮LED灯。 ·低电平时熄灭LED灯。 (4)操作GPIO点亮LED 相关代码:![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658399875307339272.png) (5)本节收获: 学习了GPIO的主要引脚 学习了操作GPIO来点亮LED灯 ## 2.GPIO的中断 (1)主要内容 ·GPIO中断相关API ·如何使用GPIO中断相关API ·如何通过GPIO中断判断按键状态 (2)GPIO中断API介绍 其实就是wifiot_gpio.h中包含声明GPIO中断相关函数。 主要接口及其功能描述: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658400329778899183.png) (3)如何查看按键对应的GPIO引脚 F1和F2按键对应的GPIO引脚是分别是GPIO11和GPIO12,通过检测GPIO的电平信号来判断按键的状态。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658400428313648516.png)
-
## 软件定时器 ------ ### 软件定时器基本概念 ------ 软件定时器:基于系统Tick时钟中断由软件模拟的定时器,构架于硬件定时器之上。相比硬件定时器提供了更多的定时器数量。 软件定时器功能上支持: ●静态裁剪:能通过宏关闭软件定时器功能。 ●软件定时器创建。 ●软件定时器启动。 ●软件定时器停止。 ●软件定时器删除。 ●软件定时器剩余Tick数获取。 ### 软件定时器运作机制 ------ **软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出,定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。** 软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,LiteOS会根据当前系统Tick时间及用户设置的定时间隔确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时*全局链表*。. 当Tick中断到来时,在Tick中断处理函数中**扫描**软件定时器的*计时全局链表*,看是否有定时器超时,若有则将超时的定时器记录下来。Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用之前记录下来的定时器的超时回调函数。 ### 软件定时器创建 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394466050944442.png) **vs code内路径如下** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394481139827804.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394482481129656.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394487295251843.png) 执行一系列操作后,编译。 ## 信号量 ### 信号量的基本概念 ------ 1.信号量是实现任务间通信的机制,达到任务间同步或临界资源的互斥访问。 2.提供同步或互斥实现临界资源的保护 3.信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。含义分两种: 1)0,表示没有积累下来的Post信号量操作,且有可能再次信号量上 阻塞的任务 2)正值,表示有一个或多个Post信号量操作。 4、以同步为目的的信号量和以互斥为目的的信号量在使用有如下不同: **1)用作互斥时**,信号量创建后记数是满的,在需要使用临界资源时,先取信号量,使其变 空,这样其他任务需要使用临界资源时就会因为无法取到信号量而阻塞,从而保证了临界资 源的安全。 **2)用作同步时**,信号量在创建后被置为空,任务1取信号量而阻塞,任务2在某种条件发生后, 释放信号量,于是任务1得以进入READY或RUNNING态,从而达到了两个任务间的同步。 ### 信号量运作机制 ------ #### 运作原理 1、信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,受内存限制), 并把 所有的信号量初始化成未使用,并加入到未使用链表中供系统使用。 2、信号量创建,从未使用的信号量链表中获取-个信号量资源,并设定初值。 3、信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释 放该信号量,等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待 任务队列的队尾。 4、信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待 任务队列上的第一个任务。 5、信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。 6、信号量允许多个任务在同- -时刻访问同- -资源,但会限制同一时刻访问此资源的最大任务数目。访问同一资源的任务数达到该资源的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。 #### 实现信号量功能 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394505266232641.png) ## 事件管理 ### 实现事件功能 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394521597802008.png) ## 互斥锁 独占式访问,同一时间只能一个进入公共资源 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394779095181242.png) ## 消息队列 1.用于任务通信,接受任务或中断的不固定长度信息,并根据接口选择是否存放。 2.任务可从队列读取信息,信息为空,挂起读取任务;有新消息,唤醒任务 3.提供异步处理,允许蒋雄安熙放入队列,但并不立即处理,同时对列起缓冲消息作用 **LiteOS用队列实现异步通信特性如下:** ●消息以先进先出方式排队,支持异步读写工作方式。 ●读队列和写队列都支持超时机制。 ●发送消息类型由通信双方约定,可以允许不同长度(不超过队列节点最大值)消息。 ●一个任务能够从任意一 个消息队列接收和发送消息。。多个任务能够从同一个消息队列接收 和发送消息。 ●当队列使用结束后,如果是动态申请的内存,需要通过释放内存函数回收。 #### 运作原理: 创建队列时,根据用户传入队列长度和消息节点大小来开辟相应 的内存空间以供该队列使用,返回队列ID。 在队列控制块中维护一个消息头节点位置Head和一个消息尾节点 位置Tail来表示当前队列中消息存储情况。Head表示队列中被占 用消息的起始位置。Tail表示队列中被空闲消息的起始位置。刚创 建时Head和Tail均指向队列起始位置。 写队列时,根据Tail找到被占用消息节点末尾的空闲节点作为数据 写入对象。 读队列时,根据Head找到最先写入队列中的消息节点进行读取。 删除队列时,根据传入的队列ID寻找到对应的队列,把队列状态 置为未使用,释放原队列所占的空间,对应的队列控制头置为初 始状态。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394804745794960.png) #### 实现消息队列功能 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394817815916576.png) ## 操作GPIO ### 接口简介 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394833464102972.png) ### 查看LED对应的GPIO引脚 ------ LED对应的GPIO引脚是GPIO2通过控制GPIO2输出的的电平信号来实 现LED灯的闪烁(高亮低灭)。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394848591357658.png) 路径:sample/B1_basic_led_blink/led_example(.c) ## GPIO中断 ### 中断API介绍 ------ **wifilot_gpio.h接口简介:** wifiliot gpio.h中包含声明GPIO中断相关函数。 | 接口名 | 功能描述 | | :-------------------- | ------------------------- | | GpioRegisterlsrFunc | 设置GPIO引脚中断功能| | GpioUnregisterlsrFunc | 取消GPIO引脚中断功能| | GpioSetlsrMask | 屏蔽GPIO引脚中断功能| | GpioSetlsrMode | 设置GPIO引脚中断触发模式 | ### 按键对应的GPIO ------ **F1**和**F2**按键对应的GPIO引脚是分别是**GPIO11**和**GPIO12**,通过检测GPIO的电平信号来判断按键的状态。 SPI和按键用用一个接口通过电阻连接,当使用SPI时不可操作按键。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394905461509991.png) ### 驱动子系统开发之PWM ------ ### API介绍 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394928054343244.png) ### LED对应的GPIO引脚 ------ LED对应的GPIO引脚是**GPIO2**通过控制GPIO2输出的的电平信号来实现LED灯的闪烁(高亮低灭)。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394942397480815.png) ### 输出PWM改变LED亮度 ------ 打开"B3_ basic pwm_ led" 工程的pwm_ example.c文件,可在代码中查看实现输出不同占空比的PWM来改变LED的亮度代码。 通过以下路径查看pwm接口,并在component内修改接口: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394957322310965.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394968818285715.png) ## 驱动子系统开发之ADC(模拟数字转换器,将模拟信号转化为数字信号的电子元件) ### 相关API ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658394997230329169.png) ### 查看ADC对应的GPIO引脚 ------ 本案例将使用板载用户按键F1来模拟GPIO口电压的变化。GPIO_ 11对应的是ADC Channel 5 ,所以需要编写软件去读取ADC Channel 5的电压。 ## I2C总线 ------ ### API介绍: ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658395024504158465.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658395031291370644.png) ### I2C读写NFC芯片 ------ NFC芯片的I2C对应的GPIO引脚是分别是GPIO0和GPIO1,所以需要编写软件使用GPIO_ 0和GPIO 1产生I2C信号去控制NFC芯片。 ## UART ------ ### API介绍 ------ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658395047034634500.png) #### UART对应的接口 ------ UART1对应的GPIO引脚是分别是**GPIO5和GPIO6**,将使用GPIO5和GPIO6进行UART数据的收发。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/21/1658395057851790183.png)
-
GPIO:扩展实例代码(B1):#include <stdio.h>#include <unistd.h>#include "ohos_init.h"#include "cmsis_os2.h"#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"static void LedTask(void){//初始化GPIOGpioInit();//设置GPIO_2的复用功能为普通GPIOIoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);//设置GPIO_2为输出模式GpioSetDir(WIFI_IOT_GPIO_IDX_2, WIFI_IOT_GPIO_DIR_OUT);WifiIotGpioDir val = {0};//定义读取变量valGpioGetDir(WIFI_IOT_GPIO_IDX_2, &val);//获取WIFI_IOT_GPIO_IDX_2的引脚方向,并赋值给变量valprintf("GPIO_2 Dir is %d\r\n", val);WifiIotGpioValue OutputVal = {0};//定义读取变量OutputValwhile (1) {//设置GPIO_2输出高电平点亮LED灯GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 1);GpioGetOutputVal(WIFI_IOT_GPIO_IDX_2, &OutputVal);//读取WIFI_IOT_GPIO_IDX_2的高电平,并赋值给变量OutputValprintf("GPIO_2 OutputVal is %d\r\n", OutputVal);//延时1susleep(1000000);//设置GPIO_2输出低电平熄灭LED灯GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 0);GpioGetOutputVal(WIFI_IOT_GPIO_IDX_2, &OutputVal);//读取WIFI_IOT_GPIO_IDX_2的低电平,并赋值给OutputValprintf("GPIO_2 OutputVal is %d\r\n", OutputVal);//延时1susleep(1000000); }}static void LedExampleEntry(void){osThreadAttr_t attr;attr.name = "LedTask";attr.attr_bits = 0U;attr.cb_mem = NULL;attr.cb_size = 0U;attr.stack_mem = NULL;attr.stack_size = 1024*2;//要打印字符,需要增大任务的空间attr.priority = 25;if (osThreadNew((osThreadFunc_t)LedTask, NULL, &attr) == NULL) {printf("Falied to create LedTask!\n"); }}APP_FEATURE_INIT(LedExampleEntry);GPIO中断:扩展实例代码(B2):#include <stdio.h>#include <unistd.h>#include "ohos_init.h"#include "cmsis_os2.h"#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"static void F1_Pressed(char *arg){ (void)arg;GpioSetOutputVal(WIFI_IOT_IO_NAME_GPIO_2, 1);//点亮LEDprintf("This is F1_Pressed\r\n");GpioSetIsrMode(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_GPIO_EDGE_RISE_LEVEL_HIGH);//将WIFI_IOT_IO_NAME_GPIO_11的模式修改为电平由低到高才触发中断}static void F2_Pressed(char *arg){ (void)arg;GpioSetOutputVal(WIFI_IOT_IO_NAME_GPIO_2, 0);//熄灭LEDprintf("This is F1_Pressed\r\n");GpioSetIsrMask(WIFI_IOT_IO_NAME_GPIO_12, 1);//屏蔽WIFI_IOT_IO_NAME_GPIO_12的中断功能}static void ButtonExampleEntry(void){GpioInit();//初始化LED灯IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);GpioSetDir(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_GPIO_DIR_OUT);//初始化F1按键,设置为下降沿触发中断(即电平由高到低触发中断)IoSetFunc(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_IO_FUNC_GPIO_11_GPIO);GpioSetDir(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_GPIO_DIR_IN);IoSetPull(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_IO_PULL_UP);GpioRegisterIsrFunc(WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_IO_NAME_GPIO_11, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, F1_Pressed, NULL);//初始化F2按键,设置为下降沿触发中断(即电平由高到低触发中断)IoSetFunc(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_IO_FUNC_GPIO_12_GPIO);GpioSetDir(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_GPIO_DIR_IN);IoSetPull(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_IO_PULL_UP);GpioRegisterIsrFunc(WIFI_IOT_IO_NAME_GPIO_12, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, F2_Pressed, NULL);}APP_FEATURE_INIT(ButtonExampleEntry);PWM:扩展实例代码(B3):#include <stdio.h>#include <unistd.h>#include "ohos_init.h"#include "cmsis_os2.h"#include "wifiiot_pwm.h"#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"#define PWM_TASK_STACK_SIZE 512#define PWM_TASK_PRIO 25static void PWMTask(void){unsigned int i;//初始化GPIOGpioInit();//设置GPIO_2引脚复用功能为PWMIoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_PWM2_OUT);//设置GPIO_2引脚为输出模式GpioSetDir(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_GPIO_DIR_OUT);//初始化PWM2端口PwmInit(WIFI_IOT_PWM_PORT_PWM2);uint8_t j = 0;while (1) {for (i = 0; i < 40000; i += 100) {//输出不同占空比的PWM波PwmStart(WIFI_IOT_PWM_PORT_PWM2, i, 40000);//PwmStop(WIFI_IOT_PWM_PORT_PWM2);//在PWM刚输出时即停止,灯仍然闪烁,但非常暗usleep(10); }i = 0;j++;if(j == 5){PwmDeinit(WIFI_IOT_PWM_PORT_PWM2);//当LED灯闪烁5次后,取消初始化PWM,LED灯始终保持最亮 } }}static void PWMExampleEntry(void){osThreadAttr_t attr;attr.name = "PWMTask";attr.attr_bits = 0U;attr.cb_mem = NULL;attr.cb_size = 0U;attr.stack_mem = NULL;attr.stack_size = 512;attr.priority = 25;if (osThreadNew((osThreadFunc_t)PWMTask, NULL, &attr) == NULL) {printf("Falied to create PWMTask!\n"); }}APP_FEATURE_INIT(PWMExampleEntry);ADC:BearPi_HM Nano芯片手册位置:./my_bearpi_hm_nano/applocations/BearPi/BearPi-HM_Nano/I2C:如果想让手机触碰NFC设备自动打开网页,要将网址写入位置设置为NDEFFirstPos
-
新增功能&功能变化介绍交付特性 (特性&价值描述)T10语言迁移优化:支持go语言源码分析. Fortran语言迁移增强.C/C++语言源码迁移增强·支持Python/Java/Scala解释型语言的扫描分析内存一致性:扩展语言支持场景,对关键算法进行改进 ·增加自动修复功能 ·支持下载静态检查工具产生的BC文件 ·支持自动生成目标工程的中间文件软件迁移评估:Jar、War包扫描提示优化,识别鲲鹏平台已经支持的依赖,在报告中给出提示;专项软件迁移:·扩充“一键式软件迁移”支持的软件范围:增加HPC场景5款软件的过移文持;支持Top16款操作系统: 新增支持openEuler 20.03(LTS-SP1) 、openEuler 20.03 (LTS-uoS Server 20 Euler (1000).BC- Linux 7.6、BC- Linux 7.7和普华(iSoft)5.1六款操作系统运行环境和目标操作系统;源码迁移识别C/C+/Fortran/汇编源代码,提供修改建议;Make、CMake、Automake编译选项、编译宏的解析及迁移建议支持100%Intrinsic函数转换(6000+个),包括MMX 、SSE及AVX Intrinsic等支持更多的Fortran内联函数和诰法特性以及编译选项的识别支持Go语言迁移,对go程序使用cgo编译部分中的编译选项、宏定义提供兼容性检查,给出修改建议支持python、Java、Scala语言,对程序中的动态链接库提供兼容性检查,给出修改建议新增支持openEuler、BC_Linux等六款主流操作系统C/C++x86Intrinsic函数实现了一键式自动迁移;平台相关宏扫描准确率和覆盖率提升;构建文件编译选项、宏定义识别率提升。FortranFortran内联函数识别(增加xx条),增加Fortran语法特性解析,整体准确率提升70%;构建文件中针对Fortran语言中所使用编译选项的识别率和准确率达到100%。Go对编译选项、宏定义、依赖库提供兼容性检查,一键下载替换。Python/Java/Scala识别源码中的加载动态库的函数;识别依赖库文件,进行兼容性检查,一键下载替换。
-
## 1. 前言 为了缓解学习、生活、工作带来的压力,提升生活品质,许多人喜欢在家中、办公室等场所养鱼。为节省鱼友时间、劳力、增加养鱼乐趣;为此,本文基于STM32单片机设计了一款基于物联网的智能鱼缸。该鱼缸可以实现水温检测、水质检测、自动或手动换水、氛围灯灯光变换和自动或手动喂食等功能为一体的控制系统,鱼缸通过ESP8266连接华为云IOT物联网平台,并通过应用侧接口开发了上位机APP实现远程对鱼缸参数检测查看,并能远程控制。 **从功能上分析,需要用到的硬件如下:** (1)STM32系统板 (2)水温温度检测传感器: 测量水温 (3)水质检测传感器: 测量水中的溶解性固体含量,反应水质。 (4)步进电机: 作为鱼饲料投食器 (5)RGB氛围灯: 采用RGB 3色灯,给鱼缸照明。 (6)抽水电动马达: 用来给鱼缸充氧,换水,加水等。 (7)ESP8266 WIFI:设置串口协议的WIFI,内置了TCP/IP协议栈,完善的AT指令,通过简单的指令就可以联网通信,但是当前采用的ESP8266没有烧写第三方固件,采用原本的原滋原味的官方固件,没有内置MQTT协议,代码里连接华为云物联网平台需要使用MQTT协议,所以在STM32代码里通过MQTT协议文档的字段结构自己实现了MQTT协议,在通过ESP8266的TCP相关的AT指令完成数据发送接收,完成与华为云IOT平台交互。 水产养殖水质常规检测的传感器有哪些?水产养殖水质常规检测的传感器有水质ph传感器、溶解氧传感器和温度传感器。 (1)水质ph传感器: ph传感器是高智能化在线连续监测仪,由传感器和二次表两部分组成。可配三复合或两复合电极,以满足各种使用场所。配上纯水和超纯水电极,可适用于电导率小于3μs/cm的水质(如化学补给水、饱和蒸气、凝结水等)的pH值测量。 (2)溶解氧传感器: 氧气的消耗量与存在的氧含量成正比,而氧是通过可透膜扩散进来的。传感器与专门设计的监测溶氧的测量电路或电脑数据采集系统相连。 溶解氧传感器能够空气校准,一般校准所需时间较长,在使用后要注意保养。如果在养殖水中工作时间过长,就必须定期地清洗膜,对其进行额外保养。 在很多水产养殖中,每天测几次溶氧就可以了解溶氧情况。对池塘和许多水槽养殖系统。溶氧水平不会变化很快,池塘一般每天检测2~3次。 对于较高密度养殖系统,增氧泵故障发生可能不到1h就会造成鱼虾等大面积死亡。这些密度高的养殖系统要求有足够多的装备或每小时多次自动测量溶氧。 (3)温度传感器: 温度传感器有多种结构,包括热电偶、电阻温度传感器和热敏电阻。热电偶技术成熟,应用领域广,货源充足。选择热电偶必须满足温度范围要求,且其材料与环境相容。 电阻温度传感器(RTDs)的原理为金属的电阻随温度的改变而改变。大多电阻温度传感器(RTDs)由铂、镍或镍合金制成,其线性度比热电偶好,热切更加稳定,但容易破碎。 热敏电阻是电阻与温度具有负相关关系的半导体。热敏电阻比RTD和热电偶更灵敏,也更容易破碎,不能承受大的温差,但这一点在水产养殖中不成问题。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201716792920061.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201733037441163.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201746248299756.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201759333914668.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657965209488421541.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657965246247913333.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657965261527177785.png) ## 2. 硬件选型 ### 2.1 STM32开发板 主控CPU采用STM32F103RCT6,这颗芯片包括48 KB SRAM、256 KB Flash、2个基本定时器、4个通用定时器、2个高级定时器、51个通用IO口、5个串口、2个DMA控制器、3个SPI、2个I2C、1个USB、1个CAN、3个12位ADC、1个12位DAC、1个SDIO接口,芯片属于大容量类型,配置较高,整体符合硬件选型设计。当前选择的这款开发板自带了一个1.4寸的TFT-LCD彩屏,可以显示当前传感器数据以及一些运行状态信息。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201867380976488.png) ### 2.2 杜邦线 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201877427828564.png) ### 2.3 PCB板 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201887682729967.png) ### 2.4 步进电机 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201904611662942.png) ### 2.5 抽水马达 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201918239686520.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201931290695945.png) ### 2.6 水温检测传感器 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201943249531814.png) 测温采用DS18B20,DS18B20是常用的数字温度传感器,其输出的是数字信号,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。 DS18B20数字温度传感器接线方便,封装成后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式,型号多种多样,有LTM8877,LTM8874等等。 主要根据应用场合的不同而改变其外观。封装后的DS18B20可用于电缆沟测温,高炉水循环测温,锅炉测温,机房测温,农业大棚测温,洁净室测温,弹药库测温等各种非极限温度场合。耐磨耐碰,体积小,使用方便,封装形式多样,适用于各种狭小空间设备数字测温和控制领域。 ### 2.7 水质检测传感器 TDS (Total Dissolved Solids)、中文名总溶解固体、又称溶解性固体、又称溶解性固体总量、表明1升水肿容有多少毫克溶解性固体、一般来说、TDS值越高、表示水中含有溶解物越多、水就越不洁净、虽然在特定情况下TDS并不能有效反映水质的情况、但作为一种可快速检测的参数、TDS目前还可以作为有效的在水质情况反映参数来作为参考。常用的TDS检测设备为TDS笔、虽然价格低廉、简单易用、但不能把数据传给控制系统、做长时间的在线监测、并做水质状况分析、使用专门的仪器、虽然能传数据、精度也高、但价格很贵、为此这款TDS传感器模块、即插即用、使用简单方便、测量用的激励源采用交流信号、可有效防止探头极化、延长探头寿命的同时、也增加了输出信号的稳定性、TDS探头为防水探头、可长期侵入水中测量、该产品可以应用于生活用水、水培等领域的水质检测、有了这个传感器、可轻松DIY--套TDS检测仪了、轻松检测水的洁净程度。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657201962217614386.png) ### 2.8 ESP8266 ■模块采用串口(LVTTL) 与MCU (或其他串口设备) 通信,内置TCP/IP协议栈,能够实现串口与WIFI之间的转换 ■模块支持LVTTL串口, 兼容3..3V和5V单片机系统 ■模块支持串 口转WIFI STA、串口转AP和WIFI STA+WIFI AP的模式,从而快速构建串口-WIFI数据传输方案 ■模块小巧(19mm*29mm), 通过6个2.54mm间距排针与外部连接 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657202982533373995.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203009974462868.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203021370179645.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203033198961449.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203069365745560.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657203091092707700.png) ## 3. 华为云IOT产品与设备创建 ### 3.1 创建产品 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657629970229570879.png) 链接:https://www.huaweicloud.com/product/iothub.html 点击右上角窗口创建产品。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630053050713019.png) 填入产品信息。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630110545674495.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630134526799399.png) 接下来创建模型文件: 创建服务。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630672668710473.png) 创建属性。根据鱼缸设备的传感器属性来添加属性。 (1)LED氛围灯 (2)抽水电机 (3)水质传感器 (4)水温温度计 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630781773437369.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630839423167449.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630867501379876.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657631378011982641.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657631432204905242.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657631458486256676.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657631475680495731.png) ### 3.2 创建设备 地址: https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/device/all-device 点击右上角创建设备。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630191959234433.png) 按照设备的情况进行填写信息。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630241007922609.png) 设备创建后保存信息: ```cpp { "device_id": "62cd6da66b9813541d510f64_dev1", "secret": "12345678" } ``` 创建成功。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630344497731008.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220712/1657630379665587266.png) ### 3.3 设备模拟调试 为了测试设备通信的过程,在设备页面点击调试。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220713/1657719675025872160.png) 选择设备调试: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220713/1657719801527206113.png) ### 3.4 MQTT三元组 为了方便能够以真实的设备登陆服务器进行测试,接下来需要先了解MQTT协议登录需要的参数如何获取,得到这些参数才可以接着进行下一步。 MQTT(Message Queuing Telemetry Transport)是一个基于客户端-服务器的消息发布/订阅传输协议,主要应用于计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备,适合长连接的场景,如智能路灯等。 MQTTS是MQTT使用TLS加密的协议。采用MQTTS协议接入平台的设备,设备与物联网平台之间的通信过程,数据都是加密的,具有一定的安全性。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657962773440848834.png) 采用MQTT协议接入物联网平台的设备,设备与物联网平台之间的通信过程,数据没有加密,如果要保证数据的私密性可以使用MQTTS协议。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657962863655935900.png) 在这里可以使用华为云提供的工具快速得到MQTT三元组进行登录。 [https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112](https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657963111182338693.png) 工具的页面地址: [https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/](https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/) 根据提示填入信息,然后生成三元组信息即可。 这里填入的信息就是在创建设备的时候生成的信息。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657963173477286324.png) ```cpp DeviceId 62cd6da66b9813541d510f64_dev1 DeviceSecret 12345678 ClientId 62cd6da66b9813541d510f64_dev1_0_0_2022071609 Username 62cd6da66b9813541d510f64_dev1 Password a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf ``` ### 3.5 平台接入地址 华为云的物联网服务器地址在这里可以获取: [https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home](https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657963631268358095.png) ```cpp MQTT (1883) a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com 对应的IP地址是: 121.36.42.100 ``` ### 3.6 MQTT的主题订阅与发布格式 得到三元组之后,就可以登录MQTT服务器进行下一步的主题发布与订阅。 主题的格式详情: [https://support.huaweicloud.com/api-iothub/iot_06_v5_3004.html](https://support.huaweicloud.com/api-iothub/iot_06_v5_3004.html) 上传的数据格式详情: [https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112](https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112) ```cpp 设备消息上报 $oc/devices/{device_id}/sys/messages/up 平台下发消息给设备 $oc/devices/{device_id}/sys/messages/down 上传的消息格式: { "services": [{ "service_id": "Connectivity", "properties": { "dailyActivityTime": 57 }, "event_time": "20151212T121212Z" }, { "service_id": "Battery", "properties": { "batteryLevel": 80 }, "event_time": "20151212T121212Z" } ] } ``` 根据当前设备的格式总结如下: ```cpp ClientId 62cd6da66b9813541d510f64_dev1_0_0_2022071609 Username 62cd6da66b9813541d510f64_dev1 Password a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf //订阅主题: 平台下发消息给设备 $oc/devices/62cd6da66b9813541d510f64_dev1/sys/messages/down //设备上报数据 $oc/devices/62cd6da66b9813541d510f64_dev1/sys/properties/report //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "fish","properties":{"LED":1}},{"service_id": "fish","properties":{"motor":1}},{"service_id": "fish","properties":{"水温":36.2}}]} ``` ### 3.6 MQTT客户端模拟设备调试 得到信息之后,将参赛填入软件进行登录测试。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657964427885652042.png) 数据发送之后,在设备页面上可以看到设备已经在线了,并且收到了上传的数据。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220716/1657964467641641236.png) ## 4. STM32程序设计 ### 4.1 硬件连线 ```cpp 硬件连接方式: 1. TFT 1.44 寸彩屏接线 GND 电源地 VCC 接5V或3.3v电源 SCL 接PC8(SCL) SDA 接PC9(SDA) RST 接PC10 DC 接PB7 CS 接PB8 BL 接PB11 2. 板载LED灯接线 LED1---PA8 LED2---PD2 3. 板载按键接线 K0---PA0 K1---PC5 K2---PA15 4. DS18B20温度传感器接线 DQ->PC6 + : 3.3V - : GND 5. 步进电机 ULN2003控制28BYJ-48步进电机接线: ULN2003接线: IN-D: PB15 d IN-C: PB14 c IN-B: PB13 b IN-A: PB12 a + : 5V - : GND 6. 抽水电机 GND---GND VCC---5V AO----PA4 7. 水质检测传感器 AO->PA1 + : 3.3V - : GND 8. RGB灯 PC13--R PC14--G PC15--B 9. ATK-ESP8266 WIFI接线 PA2(TX)--RXD 模块接收脚 PA3(RX)--TXD 模块发送脚 GND---GND 地 VCC---VCC 电源(3.3V~5.0V) ``` ### 4.2 硬件原理图 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657202192754191355.png) ### 4.3 汉字取模 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657202211768465094.png) ### 4.4 程序下载 下载软件在资料包里。点击开始编程之后,点击开发板的复位键即可下载程序进去。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220707/1657202235357606354.png) ### 4.5 主要的信息连接代码 ```cpp #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include #include "timer.h" #include "esp8266.h" #include "mqtt.h" #include "oled.h" #include "fontdata.h" #include "bh1750.h" #include "iic.h" #include "sht3x.h" #define ESP8266_WIFI_AP_SSID "aaa" //将要连接的路由器名称 --不要出现中文、空格等特殊字符 #define ESP8266_AP_PASSWORD "12345678" //将要连接的路由器密码 //华为云服务器的设备信息 #define MQTT_ClientID "62cd6da66b9813541d510f64_dev1_0_0_2022071609" #define MQTT_UserName "62cd6da66b9813541d510f64_dev1" #define MQTT_PassWord "a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf" //订阅与发布的主题 #define SET_TOPIC "$oc/devices/62cd6da66b9813541d510f64_dev1/sys/messages/down" //订阅 #define POST_TOPIC "$oc/devices/62cd6da66b9813541d510f64_dev1/sys/properties/report" //发布 ``` ### 4.6 ESP8266主要代码 ```cpp u8 ESP8266_IP_ADDR[16]; //255.255.255.255 u8 ESP8266_MAC_ADDR[18]; //硬件地址 /* 函数功能: ESP8266命令发送函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_SendCmd(char *cmd) { int RX_CNT=0; u8 i,j; for(i=0;i10;i++) //检测的次数--发送指令的次数 { USARTx_StringSend(USART3,cmd); for(j=0;j100;j++) //等待的时间 { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"OK")) { return 0; } } } } return 1; } /* 函数功能: ESP8266硬件初始化检测函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_Init(void) { //退出透传模式 USARTx_StringSend(USART3,"+++"); delay_ms(100); //退出透传模式 USARTx_StringSend(USART3,"+++"); delay_ms(100); return ESP8266_SendCmd("AT\r\n"); } /* 函数功能: 一键配置WIFI为AP+TCP服务器模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) u16 port 创建的服务器端口号 函数返回值: 0表示成功 其他值表示对应错误值 */ u8 ESP8266_AP_TCP_Server_Mode(char *ssid,char *pass,u16 port) { char *p; u8 i; char ESP8266_SendCMD[100]; //组合发送过程中的命令 /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=2\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 设置WIFI的AP模式参数*/ sprintf(ESP8266_SendCMD,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 开启多连接*/ if(ESP8266_SendCmd("AT+CIPMUX=1\r\n"))return 7; /*8. 设置服务器端口号*/ sprintf(ESP8266_SendCMD,"AT+CIPSERVER=1,%d\r\n",port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 查询本地IP地址*/ if(ESP8266_SendCmd("AT+CIFSR\r\n"))return 9; //提取IP地址 p=strstr((char*)USART3_RX_BUF,"APIP"); if(p) { p+=6; for(i=0;*p!='"';i++) { ESP8266_IP_ADDR[i]=*p++; } ESP8266_IP_ADDR[i]='\0'; } //提取MAC地址 p=strstr((char*)USART3_RX_BUF,"APMAC"); if(p) { p+=7; for(i=0;*p!='"';i++) { ESP8266_MAC_ADDR[i]=*p++; } ESP8266_MAC_ADDR[i]='\0'; } //打印总体信息 printf("当前WIFI模式:AP+TCP服务器\r\n"); printf("当前WIFI热点名称:%s\r\n",ssid); printf("当前WIFI热点密码:%s\r\n",pass); printf("当前TCP服务器端口号:%d\r\n",port); printf("当前TCP服务器IP地址:%s\r\n",ESP8266_IP_ADDR); printf("当前TCP服务器MAC地址:%s\r\n",ESP8266_MAC_ADDR); return 0; } /* 函数功能: TCP服务器模式下的发送函数 发送指令: */ u8 ESP8266_ServerSendData(u8 id,u8 *data,u16 len) { int RX_CNT=0; u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d,%d\r\n",id,len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j10;j++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n200;n++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"SEND OK")) { return 0; } } } } } } } return 1; } /* 函数功能: 配置WIFI为STA模式+TCP客户端模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) char *p 将要连接的服务器IP地址 u16 port 将要连接的服务器端口号 u8 flag 1表示开启透传模式 0表示关闭透传模式 函数返回值:0表示成功 其他值表示对应的错误 */ u8 ESP8266_STA_TCP_Client_Mode(char *ssid,char *pass,char *ip,u16 port,u8 flag) { char ESP8266_SendCMD[100]; //组合发送过程中的命令 //退出透传模式 //USARTx_StringSend(USART3,"+++"); //delay_ms(50); /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=1\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 配置将要连接的WIFI热点信息*/ sprintf(ESP8266_SendCMD,"AT+CWJAP=\"%s\",\"%s\"\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 设置单连接*/ if(ESP8266_SendCmd("AT+CIPMUX=0\r\n"))return 7; /*8. 配置要连接的TCP服务器信息*/ sprintf(ESP8266_SendCMD,"AT+CIPSTART=\"TCP\",\"%s\",%d\r\n",ip,port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 开启透传模式*/ if(flag) { if(ESP8266_SendCmd("AT+CIPMODE=1\r\n"))return 9; //开启 if(ESP8266_SendCmd("AT+CIPSEND\r\n"))return 10; //开始透传 if(!(strstr((char*)USART3_RX_BUF,">"))) { return 11; } //如果想要退出发送: "+++" } printf("WIFI模式:STA+TCP客户端\r\n"); printf("Connect_WIFI热点名称:%s\r\n",ssid); printf("Connect_WIFI热点密码:%s\r\n",pass); printf("TCP服务器端口号:%d\r\n",port); printf("TCP服务器IP地址:%s\r\n",ip); return 0; } /* 函数功能: TCP客户端模式下的发送函数 发送指令: */ u8 ESP8266_ClientSendData(u8 *data,u16 len) { int RX_CNT=0; u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d\r\n",len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j10;j++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n200;n++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"SEND OK")) { return 0; } } } } } } } return 1; } ```
-
1.开发板介绍E53扩展版接口WiFi Soc Hi3861NFC芯片NT3H120Type-C USB接口复位按键用户按键:可以通过检测GPIO11/12口的电压的变化来检测是否按下NFC天线:可用于实现碰一碰互联网,碰一碰拉起服务等实验TTL转USB天线芯片CH340E板上的LED灯可以通过GPIO2来控制E53接口1.SPI时钟引脚2.SPI片选引脚,可以是硬件SPI片选,也可以是软件SPI片选3.NC引脚,防呆设计,主板排座的该引脚需要堵孔,扩展板排针的该引脚需要剪断4.普通GPIO引脚5.ADC采集引脚6.DAC模拟量输出引脚7.普通GPIO引脚8.普通GPIO引脚,主板的该引脚必须有PWM9.lIC的时钟引脚10.lIC的数据引脚11.普通GPIO引脚,主板的该引脚必须有pWM波功能12.串口的数据接收引脚13.串口的数据发送引脚14.普通GPIO引脚,主板的该引脚必须有pWM波功能15.SPI主设备数据输出,从设备数据输入16.SPI主设备数据输入,从设备数据输出17.电源地18.3.3V电源,需保证能提供2A的电流19.电源地20.5.0V电源,需保证能提供2A的电流
-
一、前言物联网是互联网基础上的延伸和扩展的网络,将各种信息传感设备与互联网结合起来而形成的一个巨大网络,实现在任何时间、任何地点,人、机、物的互联互通。物联网的底层是感知层,感知层主要器件是传感器,作用是使用传感器收集信息;收集到的信息会发给传输层,传输层的核心的无线网络(WiFi,蓝牙,zigbee等),作用是将感知层收集的信息传输给上层应用层,应用层是所谓的云服务器。应用层通过大数据,云计算等手段最终得出结论,在通过传输层发出操作指令给底层去执行。单片机是物联网感知层的核心,而接在单片机上的各种传感器就是单片机的感官,通过这些传感器采集数据,经过单片机统一处理传递给应用终端进行统一分析,完成互联互通。这篇文章合集就列出了单片机的常用传感器开发案例,比如:温度湿度传感器、GPS定位数据解析与转换、ESP8266串口WIFI的使用、光敏传感器的应用案例、三轴陀螺仪的使用案例、OLED低功耗显示屏应用案例、步进电机应用案例、IIC通信协议详解、红外线通信协议应用方案、单片机在线升级方案等等。二、开发案例2.1 STM32+BH1750光敏传感器获取光照强度链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193508这篇文章利用STM32F103读取B1750光敏传感器的数据,使用IIC模拟时序驱动,方便移植到其他平台,采集的光照度比较灵敏. 合成的光照度返回值范围是 0~255。 0表示全黑 255表示很亮。实测: 手机闪光灯照着的状态返回值是245左右,手捂着的状态返回值是10左右.2.2 STM32+MFRC522完成IC卡号读取、密码修改、数据读写链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193563这篇文章利用STM32F103控制RC522模块,使用MFRC522模块完成对IC卡卡号读取、卡类型区分、IC卡扇区密码修改、扇区数据读写等功能;底层采用SPI模拟时序,可以很方便的移植到其他设备,完成项目开发。 现在很多嵌入式方向的毕业设计经常使用到该模块,比如: 校园一卡通设计、水卡充值消费设计、公交卡充值消费设计等。2.3 STM32+HC05串口蓝牙设计简易的蓝牙音箱链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193568这篇文章介绍简易蓝牙音箱的设计流程方案,讲解STM32串口的使用,HC05蓝牙模块的使用,设计了Android上位机,Android手机打开APP,设置好参数之后,选择音乐文件发送给蓝牙音箱设备端,HC05蓝牙收到数据之后,再传递给VS1053进行播放。程序里采用环形缓冲区,接收HC05蓝牙传递的数据,设置好传递的参数之后,基本播放音乐是很流畅的。2.4 基于STM32单片机设计的红外测温仪(带人脸检测)链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193593由于医学发展的需要,在很多情况下,一般的温度计己经满足不了快速而又准确的测温要求,例如:车站、地铁、机场等人口密度较大的地方进行人体温度测量。当前设计的这款红外测温仪由测温硬件+上位机软件组合而成,主要用在地铁、车站入口等地方,可以准确识别人脸进行测温,如果有人温度超标会进行语音提示并且保存当前人脸照片。2.5 STM32+MPU6050设计便携式Mini桌面时钟(自动调整时间显示方向)链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193644Mini桌面时钟可以根据MPU6050测量的姿态自动调整显示画面方向,也就是倒着拿、横着拿、反着拿都可以让时间显示是正对着自己的,时间支持自己调整,支持串口校准。可以按键切换页面查看环境温度显示。2.6 STM32+OLED显示屏制作指针式电子钟链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193646自古以来时钟便是我们人类生活中异常重要的不可缺少的一部分。时钟可以让人们准确地了解和知道每时每刻的时间。现代生活的人们越来越重视起了时间观念,可以说是时间和金钱划上了等号,对于那些对时间把握非常严格和准确的人或事来说,时间的不准确会带来非常大的麻烦。2.7 STM32+ULN2003驱动28BYJ4步进电机(根据圈数正转、反转)链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193647采用STM32驱动28BYJ4步进电机,实现正转反转,完成角度调整。步进电机是一种将电脉冲转化为角位移的执行机构。当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动- -一个固定的角度(及步进角)。可以通过控制脉冲个来控制角位移量,从而达到准确定位的目的;同时可以通过控制脉冲频率来控制电机转动的速度和加速度,从而达到调速的目的。2.8 STM32F103ZE+SHT30检测环境温度与湿度(IIC模拟时序)链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193648sht30是盛世瑞恩生产的温湿度传感器, SHT30支持高精度温湿度测量,内部自动校准,整个程序采用模块化编程,iic时序为一个模块(iic.c 和 iic.h),SHT30为一个模块(sht30.c 和 sht30.h);IIC时序采用模拟时序方式实现,IO口都采用宏定义方式,方便快速移植到其他平台使用。2.9 STM32F103实现IAP在线升级应用程序链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193650IAP,全称是“In-Application Programming”,中文解释为“在程序中编程”。IAP是一种对通过微控制器的对外接口(如USART,IIC,CAN,USB,以太网接口甚至是无线射频通道)对正在运行程序的微控制器进行内部程序的更新的技术(注意这完全有别于ICP或者ISP技术)。ICP(In-Circuit Programming)技术即通过在线仿真器对单片机进行程序烧写,而ISP技术则是通过单片机内置的bootloader程序引导的烧写技术。无论是ICP技术还是ISP技术,都需要有机械性的操作如连接下载线,设置跳线帽等。若产品的电路板已经层层密封在外壳中,要对其进行程序更新无疑困难重重,若产品安装于狭窄空间等难以触及的地方,更是一场灾难。但若进引入了IAP技术,则完全可以避免上述尴尬情况,而且若使用远距离或无线的数据传输方案,甚至可以实现远程编程和无线编程。这绝对是ICP或ISP技术无法做到的。某种微控制器支持IAP技术的首要前提是其必须是基于可重复编程闪存的微控制器。STM32微控制器带有可编程的内置闪存,同时STM32拥有在数量上和种类上都非常丰富的外设通信接口,因此在STM32上实现IAP技术是完全可行的。2.10 STM32封装ESP8266一键配置函数:实现实现AP模式和STA模式切换、服务器与客户端创建链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193651ESP8266是一款物联网WiFi芯片,基于ESP8266可以开发物联网串口WiFi模块,像SKYLAB的WG219/WG229专为移动设备和物联网应用设计,可将用户的物理设备连接到WiFi无线网络上,进行互联网或局域网通信,实现联网功能。另外WG219/WG229仅需要通过出串口使用AT指令控制,就能满足大部分的网络功能需求。2.11 STM32入门开发 NEC红外线协议解码(超低成本无线传输方案)链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193653红外线传输协议可以说是所有无线传输协议里成本最低,最方便的传输协议了,但是也有缺点,距离不够长,速度不够快;当然,每个传输协议应用的环境不一样,定位不一样,好坏没法比较,具体要看自己的实际场景选择合适的通信方式。NEC协议是众多红外线协议中的一种(这里说的协议就是他们数据帧格式定义不一样,数据传输原理都是一样的),我们购买的外能遥控器、淘宝买的mini遥控器、电视机、投影仪几乎都是NEC协议。 像格力空调、美的空调这些设备使用的就是其他协议格式,不是NEC协议,但是只要学会一种协议解析方式,明白了红外线传输原理,其他遥控器协议都可以解出来。2.12 STM32入门开发 编写DS18B20温度传感器驱动(读取环境温度、支持级联)链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193655DS18B20是一个数字温度传感器,采用的是单总线时序与主机通信,只需要一根线就可以完成温度数据读取;DS18B20内置了64位产品序列号,方便识别身份,在一根线上可以挂接多个DS18B20传感器,通过64位身份验证,可以分别读取来至不同传感器采集的温度信息。2.13 STM32入门开发 采用IIC硬件时序读写AT24C08(EEPROM)链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193656AT24C08系列支持I2C,总线数据传送协议I2C,总线协议规定任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器;数据传送是由产生串行时钟和所有起始停止信号的主器件控制的。主器件和从器件都可以作为发送器或接收器,但由主器件控制传送数据(发送或接收)的模式。2.14 GPS原始坐标转百度地图坐标(纯C代码)链接:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=193507得到GPS原始坐标数据之后,想通过百度地图API接口直接显示实际定位。国际经纬度坐标标准为WGS-84,国内必须至少使用国测局制定的GCJ- 02,对地理位置进行首次加密。百度坐标在此基础上,进行了BD-09二次加密措施,更加保护了个人隐私。百度对外接口的坐标系并不是GPS采集的真实经 纬度,需要通过坐标转换接口进行转换。
-
# 一、环境介绍 **编程软件:** keil5 **操作系统:** win10 **MCU型号:** STM32F103ZET6 **STM32编程方式:** 寄存器开发 (方便程序移植到其他单片机) **IIC总线:** STM32本身支持IIC硬件时序的,上篇文章已经介绍了采用IIC模拟时序读写AT24C02,这篇文章介绍STM32的硬件IIC配置方法,并读写AT24C08。 模拟时序更加方便移植到其他单片机,通用性更高,不分MCU;硬件时序效率更高,每个MCU配置方法不同,依赖硬件本身支持。 **器件型号:** 采用AT24C08 EEPROM存储芯片 # 二、AT24C08存储芯片介绍 ## 2.1 芯片功能特性介绍 AT24C08 是串行CMOS类型的EEPROM存储芯片,AT24C0x这个系列包含了**AT24C01、AT24C02、AT24C04、AT24C08、AT24C16**这些具体的芯片型号。 他们容量分别是:1K (128 x 8)、2K (256 x 8)、8K (1024 x 8)、16K (2048 x 8) ,其中的8表示8位(bit) **它们的管脚功能、封装特点如下:** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116592487179368.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116601734371670.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116609274299798.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116616871150905.png) **芯片功能描述:** AT24C08系列支持I2C,总线数据传送协议I2C,总线协议规定任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器;数据传送是由产生串行时钟和所有起始停止信号的主器件控制的。主器件和从器件都可以作为发送器或接收器,但由主器件控制传送数据(发送或接收)的模式。 **芯片特性介绍:** \1. 低压和标准电压运行 –2.7(VCC=2.7伏至5.5伏) –1.8(VCC=1.8伏至5.5伏) \2. 两线串行接口(SDA、SCL) \3. 有用于硬件数据保护的写保护引脚 \4. 自定时写入周期(5毫秒~10毫秒),因为内部有页缓冲区,向AT24C0x写入数据之后,还需要等待AT24C0x将缓冲区数据写入到内部EEPROM区域. \5. 数据保存可达100年 \6. 100万次擦写周期 \7. 高数据传送速率为400KHz、低速100KHZ和IIC总线兼容。 100 kHz(1.8V)和400 kHz(2.7V、5V) \8. 8字节页写缓冲区 这个缓冲区大小与芯片具体型号有关: 8字节页(1K、2K)、**16字节页(4K、8K、16K)** ## **2.2 芯片设备地址介绍** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116640108939187.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116654449937024.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116662482639424.png) 因为IIC协议规定,每次传递数据都是按8个字节传输的,AT24C08是1024字节,地址的选择上与AT24C02有所区别; IIC设备的标准地址位是7位。上面这个图里AT24C08的1010是芯片内部固定值,A2 是硬件引脚、由硬件决定电平;P1、P0是空间存储块选择,每个存储块大小是256字节,寻址范围是0~255,AT24C08相当于是4块AT24C02的构造;最后一位是读/写位(1是读,0是写),读写位不算在地址位里,但是根据IIC的时序顺序,在操作设备前,都需要先发送7位地址,再发送1位读写位,才能启动对芯片的操作,我们在写模拟时序为了方便统一写for循环,按字节发送,所以一般都是将7地址位与1位读写位拼在一起,组合成1个字节,方便按字节传输数据。 **我现在使用的开发板上AT24C08的原理图是这样的:** ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116677233937383.png) **那么这个AT24C08的标准设备地址分别是:** 第一块区域: 0x50(十六进制),对应的二进制就是: 1010000 第二块区域: 0x51(十六进制),对应的二进制就是: 1010001 第三块区域: 0x52(十六进制),对应的二进制就是: 1010010 第四块区域: 0x53(十六进制),对应的二进制就是: 1010011 **如果将读写位组合在一起,读权限的设备地址:** 第一块区域: 0xA1(十六进制),对应的二进制就是: 10100001 第二块区域: 0xA3(十六进制),对应的二进制就是: 10100011 第三块区域: 0xA5(十六进制),对应的二进制就是: 10100101 第四块区域: 0xA7(十六进制),对应的二进制就是: 10100111 **如果将读写位组合在一起,写权限的设备地址:** 第一块区域: 0xA0(十六进制),对应的二进制就是: 10100000 第二块区域: 0xA2(十六进制),对应的二进制就是: 10100010 第三块区域: 0xA4(十六进制),对应的二进制就是: 10100100 第四块区域: 0xA6(十六进制),对应的二进制就是: 10100110 ## 2.3 对AT24C08 按字节写数据的指令流程(时序) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116690325338851.png) **详细解释:** \1. 先发送起始信号 \2. 发送设备地址(写权限) \3. 等待AT24C08应答、低电平有效 \4. 发送存储地址、AT24C08内部一共有256个字节空间,寻址是从0开始的,范围是(0~255);发送这个存储器地址就是告诉AT24C08接下来的数据改存储到哪个地方。 \5. 等待AT24C08应答、低电平有效 \6. 发送一个字节的数据,这个数据就是想存储到AT24C08里保存的数据。 \7. 等待AT24C08应答、低电平有效 \8. 发送停止信号 ## 2.3 对AT24C08 按页写数据的指令流程(时序) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116704524144178.png) **详细解释:** \1. 先发送起始信号 \2. 发送设备地址(写权限) \3. 等待AT24C08应答、低电平有效 \4. 发送存储地址、AT24C08内部一共有256个字节空间,寻址是从0开始的,范围是(0~255);发送这个存储器地址就是告诉AT24C08接下来的数据改存储到哪个地方。 \5. 等待AT24C08应答、低电平有效 \6. 可以循环发送8个字节的数据,这些数据就是想存储到AT24C08里保存的数据。 AT24C08的页缓冲区是16个字节,所有这里的循环最多也只能发送16个字节,多发送的字节会将前面的覆盖掉。 需要注意的地方: 这个页缓冲区的寻址也是从0开始,比如: 0~15算第1页,16~32算第2页......依次类推。 如果现在写数据的起始地址是3,那么这一页只剩下13个字节可以写;并不是说从哪里都可以循环写16个字节。 详细流程: 这里程序里一般使用for循环实现 (1). 发送字节1 (2). 等待AT24C08应答,低电平有效 (3). 发送字节2 (4). 等待AT24C08应答,低电平有效 ......... 最多8次. \7. 等待AT24C08应答、低电平有效 \8. 发送停止信号 ## 2.4 从AT24C08任意地址读任意字节数据(时序) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116717099155395.png) AT24C08支持当前地址读、任意地址读,最常用的还是任意地址读,因为可以指定读取数据的地址,比较灵活,上面这个指定时序图就是任意地址读。 **详细解释:** \1. 先发送起始信号 \2. 发送设备地址(写权限) \3. 等待AT24C08应答、低电平有效 \4. 发送存储地址、AT24C08内部一共有2048个字节空间,寻址是从0开始的,范围是(0~1024);发送这个存储器地址就是告诉AT24C08接下来应该返回那个地址的数据给单片机。 \5. 等待AT24C08应答、低电平有效 \6. 重新发送起始信号(切换读写模式) \7. 发送设备地址(读权限) \8. 等待AT24C08应答、低电平有效 \9. 循环读取数据: 接收AT24C08返回的数据. 读数据没有字节限制,可以第1个字节、也可以连续将整个芯片读完。 \10. 发送非应答(高电平有效) \11. 发送停止信号 # 三、IIC总线介绍 ### 2.1 IIC总线简介 I2C(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备,是微电子通信控制领域广泛采用的一种总线标准。具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。 I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 I2C 总线通过串行数据(SDA)线和串行时钟(SCL)线在连接到总线的器件间传递信息。每个器件都有一个唯一的地址识别,而且都可以作为一个发送器或接收器(由器件的功能决定)。 I2C有四种工作模式: 1.主机发送 2.主机接收 3.从机发送 4.从机接收 I2C总线只用两根线:串行数据SDA(Serial Data)、串行时钟SCL(Serial Clock)。 总线必须由主机(通常为微控制器)控制,主机产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。 SDA线上的数据状态仅在SCL为低电平的期间才能改变。 ### 2.2 IIC总线上的设备连接图 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116735029904381.png) I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。 其中上拉电阻范围是4.7K~100K。 ## 2.3 I2C总线特征 I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个从设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知)。主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。 **1. 总线上能挂接的器件数量** I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址的限制。 一般I2C设备地址是7位地址(也有10位),地址分成两部分:芯片固化地址(生产芯片时候哪些接地,哪些接电源,已经固定),可编程地址(引出IO口,由硬件设备决定)。 例如: 某一个器件是7 位地址,其中10101 xxx 高4位出厂时候固定了,低3位可以由设计者决定。 则一条I2C总线上只能挂该种器件最少8个。 如果7位地址都可以编程,那理论上就可以达到128个器件,但实际中不会挂载这么多。 **2. 总线速度传输速度:** I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整。 **3. 总线数据长度** I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。 ## 2.4 I2C总线协议基本时序信号 **空闲状态:** SCL和SDA都保持着高电平。 **起始条件:** 总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平期间而SDA由高到低的跳变,表示产生一个起始条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线。 **停止条件:** 当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。 **答应信号:** 每个字节传输完成后的下一个时钟信号,在SCL高电平期间,SDA为低,则表示一个应答信号。 **非答应信号:** 每个字节传输完成后的下一个时钟信号,在SCL高电平期间,SDA为高,则表示一个应答信号。应答信号或非应答信号是由接收器发出的,发送器则是检测这个信号(发送器,接收器可以从设备也可以主设备)。 **注意:起始和结束信号总是由主设备产生。** ## 2.5 起始信号与停止信号 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116750192127757.png) 起始信号就是: 时钟线SCL处于高电平的时候,数据线SDA由高电平变为低电平的过程。SCL=1;SDA=1;SDA=0; 停止信号就是: 时钟线SCL处于低电平的时候, 数据线SDA由低电平变为高电平的过程。SCL=1;SDA=0;SDA=1; ## 2.6 应答信号 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116760928330525.png) 数据位的第9位就时应答位。 读取应答位的流程和读取数据位是一样的。示例: SCL=0;SCL=1;ACK=SDA; 这个ACK就是读取的应答状态。 ## 2.7 数据位传输时序 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116769973115952.png) 通过时序图了解到,SCL处于高电平的时候数据稳定,SCL处于低电平的时候数据不稳定。 那么对于写一位数据(STM32--->AT24C08): SCL=0;SDA=data; SCL=1; 那么对于读一位数据(STM32 -----AT24C08): SCL=0;SCL=1;data=SDA; ## 2.8 总线时序 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116779606317263.png) # 四、IIC总线时序代码、AT24C08读写代码 在调试IIC模拟时序的时候,可以在淘宝上买一个24M的USB逻辑分析仪,时序出现问题,使用逻辑分析仪一分析就可以快速找到问题。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657116791364667781.png) ## 4.1 iic.c 这是STM32的IIC硬件时序完整代码 ```cpp /* 函数功能: 初始化IIC总线 硬件连接: SCL---PB6 SDA---PB7 */ void IIC_Init(void) { /*1. 时钟配置*/ RCC->APB2ENR|=1<<3; //PB /*2. GPIO口模式配置*/ GPIOB->CRL&=0x00FFFFFF; GPIOB->CRL|=0xFF000000; //复用开漏输出 GPIOB->ODR|=0x3<<6; /*3. GPIO口时钟配置(顺序不能错)*/ RCC->APB1ENR|=1<<21; //I2C1时钟 RCC->APB1RSTR|=1<<21; //开启复位时钟 RCC->APB1RSTR&=~(1<<21);//关闭复位时钟 /*4. 配置IIC的核心寄存器*/ I2C1->CR2=0x24<<0; //配置主机频率为36MHZ I2C1->CCR|=0x2D<<0; //配置主机频率是400KHZ I2C1->CR1|=1<<0; //开启IIC模块 /* CCR=主机时钟频率/2/IIC总线的频率 45=36MHZ/2/400KHZ ---0x2D */ } /* 函数功能: 发送起始信号 当时钟线为高电平的时候,数据线由高电平变为低电平的过程 */ void IIC_SendStart(void) { I2C1->CR1|=1<<8; //产生起始信号 while(!(I2C1->SR1&1<<0)){} //等待起始信号完成 I2C1->SR1=0; //清除状态位 } /* 函数功能: 停止信号 当时钟线为高电平的时候,数据线由低电平变为高电平的过程 */ void IIC_SendStop(void) { I2C1->CR1|=1<<9; } /* 函数功能: 发送地址数据 */ void IIC_SendAddr(u8 addr) { u32 s1,s2; I2C1->DR=addr; //发送数据 while(1) { s1=I2C1->SR1; s2=I2C1->SR2; if(s1&1<<1) //判断地址有没有发送成功 { break; } } } /* 函数功能: 发送数据 */ void IIC_SendOneByte(u8 addr) { u32 s1,s2; I2C1->DR=addr; //发送数据 while(1) { s1=I2C1->SR1; s2=I2C1->SR2; if(s1&1<<2) //判断数据有没有发送成功 { break; } } } /* 函数功能: 接收一个字节数据 */ u8 IIC_RecvOneByte(void) { u8 data=0; I2C1->CR1|=1<<10; //使能应答 while(!(I2C1->SR1&1<<6)){} //等待数据 data=I2C1->DR; I2C1->CR1&=~(1<<10); //关闭应答使能 return data; } ``` ## 4.2 AT24C08.c 这是AT24C08完整的读写代码 ```cpp * 函数功能: 写一个字节 函数参数: u8 addr 数据的位置(0~1023) u8 data 数据范围(0~255) */ void AT24C08_WriteOneByte(u16 addr,u8 data) { u8 read_device_addr=AT24C08_READ_ADDR; u8 write_device_addr=AT24C08_WRITE_ADDR; if(addr<256*1) //第一个块 { write_device_addr|=0x0<<1; read_device_addr|=0x0<<1; } else if(addr<256*2) //第二个块 { write_device_addr|=0x1<<1; read_device_addr|=0x1<<1; } else if(addr<256*3) //第三个块 { write_device_addr|=0x2<<1; read_device_addr|=0x2<<1; } else if(addr<256*4) //第四个块 { write_device_addr|=0x3<<1; read_device_addr|=0x3<<1; } addr=addr%256; //得到地址范围 IIC_SendStart();//起始信号 IIC_SendAddr(write_device_addr);//发送设备地址 IIC_SendOneByte(addr); //数据存放的地址 IIC_SendOneByte(data); //发送将要存放的数据 IIC_SendStop(); //停止信号 DelayMs(10); //等待写 } /* 函数功能: 读一个字节 函数参数: u8 addr 数据的位置(0~1023) 返回值: 读到的数据 */ u8 AT24C08_ReadOneByte(u16 addr) { u8 data=0; u8 read_device_addr=AT24C08_READ_ADDR; u8 write_device_addr=AT24C08_WRITE_ADDR; if(addr<256*1) //第一个块 { write_device_addr|=0x0<<1; read_device_addr|=0x0<<1; } else if(addr<256*2) //第二个块 { write_device_addr|=0x1<<1; read_device_addr|=0x1<<1; } else if(addr<256*3) //第三个块 { write_device_addr|=0x2<<1; read_device_addr|=0x2<<1; } else if(addr<256*4) //第四个块 { write_device_addr|=0x3<<1; read_device_addr|=0x3<<1; } addr=addr%256; //得到地址范围 IIC_SendStart();//起始信号 IIC_SendAddr(write_device_addr);//发送设备地址 IIC_SendOneByte(addr); //将要读取数据的地址 IIC_SendStart();//起始信号 IIC_SendAddr(read_device_addr);//发送设备地址 data=IIC_RecvOneByte();//读取数据 IIC_SendStop(); //停止信号 return data; } /* 函数功能: 从指定位置读取指定长度的数据 函数参数: u16 addr 数据的位置(0~1023) u16 len 读取的长度 u8 *buffer 存放读取的数据 返回值: 读到的数据 */ void AT24C08_ReadByte(u16 addr,u16 len,u8 *buffer) { u16 i=0; IIC_SendStart();//起始信号 IIC_SendAddr(AT24C08_WRITE_ADDR);//发送设备地址 IIC_SendOneByte(addr); //将要读取数据的地址 IIC_SendStart();//起始信号 IIC_SendAddr(AT24C08_READ_ADDR);//发送设备地址 for(i=0;i<len;i++) { buffer<i>=IIC_RecvOneByte();//读取数据 } IIC_SendStop(); //停止信号 } /* 函数功能: AT24C08页写函数 函数参数: u16 addr 写入的位置(0~1023) u8 len 写入的长度(每页16字节) u8 *buffer 存放读取的数据 */ void AT24C08_PageWrite(u16 addr,u16 len,u8 *buffer) { u16 i=0; IIC_SendStart();//起始信号 IIC_SendAddr(AT24C08_WRITE_ADDR);//发送设备地址 IIC_SendOneByte(addr); //数据存放的地址 for(i=0;i<len;i++) { IIC_SendOneByte(buffer<i>); //发送将要存放的数据 } IIC_SendStop(); //停止信号 DelayMs(10); //等待写 } /* 函数功能: 从指定位置写入指定长度的数据 函数参数: u16 addr 数据的位置(0~1023) u16 len 写入的长度 u8 *buffer 存放即将写入的数据 返回值: 读到的数据 */ void AT24C08_WriteByte(u16 addr,u16 len,u8 *buffer) { u8 page_byte=16-addr%16; //得到当前页剩余的字节数量 if(page_byte>len) //判断当前页剩余的字节空间是否够写 { page_byte=len; //表示一次性可以写完 } while(1) { AT24C08_PageWrite(addr,page_byte,buffer); //写一页 if(page_byte==len)break; //写完了 buffer+=page_byte; //指针偏移 addr+=page_byte;//地址偏移 len-=page_byte;//得到剩余没有写完的长度 if(len>16)page_byte=16; else page_byte=len; //一次可以写完 } } ``` ## 4.3 main.c 这是AT24C08测试代码 ```cpp #include "stm32f10x.h" #include "beep.h" #include "delay.h" #include "led.h" #include "key.h" #include "sys.h" #include "usart.h" #include <string.h> #include <stdio.h> #include "exti.h" #include "timer.h" #include "rtc.h" #include "adc.h" #include "ds18b20.h" #include "ble.h" #include "esp8266.h" #include "wdg.h" #include "oled.h" #include "rfid_rc522.h" #include "infrared.h" #include "iic.h" #include "at24c08.h" u8 buff_tx[50]="1234567890"; u8 buff_rx[50]; u8 data=88; u8 data2; int main() { u8 key; LED_Init(); KEY_Init(); BEEP_Init(); TIM1_Init(72,20000); //辅助串口1接收,超时时间为20ms USART_X_Init(USART1,72,115200); IIC_Init(); //IIC总线初始化 printf("usart1 ok\n"); while(1) { key=KEY_Scanf(); if(key) { //AT24C08_WriteByte(100,50,buff_tx); //AT24C08_ReadByte(100,50,buff_rx); //printf("buff_rx=%s\n",buff_rx); //测试第0块 // data=AT24C08_ReadOneByte(0); // AT24C08_WriteOneByte(0,data+1); // printf("data=%d\n",data); //测试第1块 // data=AT24C08_ReadOneByte(300); // AT24C08_WriteOneByte(300,data+1); // printf("data=%d\n",data); //测试第2块 // data=AT24C08_ReadOneByte(600); // AT24C08_WriteOneByte(600,data+1); // printf("data=%d\n",data); //测试第3块 data=AT24C08_ReadOneByte(900); AT24C08_WriteOneByte(900,data+1); printf("data=%d\n",data); } } } ```
-
# 一、环境介绍 **MCU:** STM32F103ZET6 **编程软件环境:** keil5 **红外线传输协议:** NEC协议---38KHZ载波:。NEC协议是红外遥控协议中常见的一种。 **解码思路:** 外部中断 + 定时器方式 **代码风格:** 模块化编程,寄存器直接操作方式 # 二、NEC协议与解码思路介绍 ## 2.1 采用的相关硬件 **图1:** 这是NEC协议的红外线遥控器: 如果自己手机没有红外线遥控器的功能,可以淘宝上买一个小遥控器来学习测试,成本不高,这个遥控器也可以自己做,能解码当然也可以编码发送,只需要一个红外光发射管即可。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115699913865031.png) **图2:** 这是红外线接收头模块。如果自己的开发板没有自带这个接收头,那就单独买一个接收头模块,使用杜邦线接到开发板的IO口上即可用来测试学习,接线很方便。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115709191819920.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115717863220745.png) **图3:** 这是红外线发射管,如果自己想做遥控器的发射端,自己做遥控器,那么就可以直接购买这种模块即可。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115733168792089.png) ## 2.2 红外线协议介绍 在光谱中波长自760nm至400um的电磁波称为红外线,它是一种不可见光。红外线通信的例子我们每个人应该都很熟悉,目前常用的家电设备几乎都可以通过红外遥控的方式进行遥控,比如电视机、空调、投影仪等,都可以见到红外遥控的影子。这种技术应用广泛,相应的应用器件都十分廉价,因此红外遥控是我们日常设备控制的理想方式。 **红外线的通讯原理:** 红外光是以特定的频率脉冲形式发射,接收端收到到信号后,按照约定的协议进行解码,完成数据传输,在消费类电子产品里,脉冲频率普遍采用 30KHz 到 60KHz 这个频段,NEC协议的频率就是38KHZ。 这个以特定的频率发射其实就可以理解为点灯,不要被复杂的词汇难住了,就是控制灯的闪烁频率(亮灭),和刚学单片机完成闪光灯一样的意思,只不过是灯换了一种类型,都是灯。 接收端的原理: 接收端的芯片对这个红外光比较敏感,可以根据有没有光输出高低电平,如果发送端的闪烁频率是有规律的,接收端收到后输出的高电平和低电平也是有规律对应的,这样发送端和接收端只要约定好,那就可以做数据传输了。 红外线传输协议可以说是所有无线传输协议里成本最低,最方便的传输协议了,但是也有缺点,距离不够长,速度不够快;当然,每个传输协议应用的环境不一样,定位不一样,好坏没法比较,具体要看自己的实际场景选择合适的通信方式。 # 2.3 NEC协议介绍 NEC协议是众多红外线协议中的一种(这里说的协议就是他们数据帧格式定义不一样,数据传输原理都是一样的),我们购买的外能遥控器、淘宝买的mini遥控器、电视机、投影仪几乎都是NEC协议。 像格力空调、美的空调这些设备使用的就是其他协议格式,不是NEC协议,但是只要学会一种协议解析方式,明白了红外线传输原理,其他遥控器协议都可以解出来。 **下图是NEC协议传输一次数据的完整格式:** ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115744483521051.png) NEC协议一次完整的传输包含: 引导码、8位用户码、8位用户反码、8位数据码、8位数据反码。 **(注意:下面的解释都是站在红外线接收端的角度来进行说明的,就是解码端的角度)** **引导码:** 由9ms的高电平+4.5ms的低电平组成。 **4个字节的数据:** 用户码+用户反码+数据码+数据反码。 这里的反码可以用来校验数据是否传输正确,有没有丢包。 **重点: NEC协议传输数据位的时候,0和1的区分是依靠收到的高、低电平的持续时间来进行区分的---这是解码关键。** 标准间隔时间:0.56ms 收到数据位0: 0.56ms 收到位1: 1.68ms 所以,收到一个数据位的完整时间表示方法是这样的: 收到数据位0: 0.56m低电平+ 0.56ms的高电平 收到数据位1: 0.56ms低电平+1.68ms的高电平 **红外线接收头模块输出电平的原理:** 红外线接收头感应到有红外光就输出低电平,没有感应到红外光就输出高电平。 **这是使用逻辑分析采集红外线接收头输出的信号:** ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115768556288931.png) **这是采集红外线遥控器上的LED灯输出电平时序图,刚好和接收端相反:** ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115780744895873.png) 单片机编写解码程序的时候,常见的方式就是采用外部中断+定时器的方式进行解析,中断可以设置为低电平触发,因为接收头没有感应到红外光默认是输出高电平,如果收到NEC引导码,就会输出低电平,进入到中断服务函数,完成解码,解码过程中开启定时器记录每一段的高电平、低电平的持续时间,按照NEC协议进行判断,完成最终解码。 STM32可以使用输入捕获方式完成解码,其实输入捕获就是外部中断+定时器的组合,只不过是STM32内部封装了一层。 **外部中断服务器里的解码程序如下(这个在其他单片机上思路是一样的):** ```cpp /* 函数功能: 外部中断线9_5服务函数 */ void EXTI9_5_IRQHandler(void) { u32 time; u8 i,j,data=0; //清除中断线9上的中断请求 EXTI->PR|=19; time=Infrared_GetTime_L(); //得到低电平时间 if(time7000||time>10000)return; //标准时间: 9000us time=Infrared_GetTime_H(); //得到高电平时间 if(time3000||time>5500)return; //标准时间4500us //正式解码NEC协议 for(i=0;i4;i++) { for(j=0;j8;j++) { time=Infrared_GetTime_L(); //得到低电平时间 if(time400||time>700)return; //标准时间: 560us time=Infrared_GetTime_H(); //得到高电平时间 if(time>1400&&time1800) //数据1 1680us { data>>=1; data|=0x80; } else if(time>400&&time700) //数据0 560us { data>>=1; } else return; } InfraredRecvData[i]=data; //存放解码成功的值 } //解码成功 InfraredRecvState=1; } ``` # 三、核心完整代码 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115799439626463.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657115811706554464.png) 本程序的解码思路是: 将红外线接收模块的输出脚接到STM32的PB9上,配置STM32的PB9为外部中断模式,下降沿电平触发;如果收到红外线信号就进入到中断服务函数里解码,如果解码过程中发现数据不符合要求就终止解码,如果数据全部符合要求就按照协议接收,直到解码完成,设置标志位,在main函数里打印解码得到的数据。 代码都是模块化编程,阅读起来也很方便。 ## 3.1 红外线解码.c ```cpp #include "nec_Infrared.h" u8 InfraredRecvData[4]; //存放红外线解码接收的数据 u8 InfraredRecvState=0; //0表示未接收到数据,1表示接收到数据 /* 函数功能: 红外线解码初始化(接收) */ void Infrared_RecvInit(void) { Infrared_Time6_Init(); //定时器初始化 /*1. 配置GPIO口*/ RCC->APB2ENR|=13; //PB GPIOB->CRH&=0xFFFFFF0F; GPIOB->CRH|=0x00000080; GPIOB->ODR|=19; /*2. 配置外部中断*/ EXTI->IMR|=19; //外部中断线9,开放中断线的中断请求功能 EXTI->FTSR|=19; //中断线9_下降沿 RCC->APB2ENR|=10; //开启AFIO时钟 AFIO->EXTICR[2]&=~(0xF1*4); AFIO->EXTICR[2]|=0x11*4; STM32_NVIC_SetPriority(EXTI9_5_IRQn,1,1); } /* 函数功能: 初始化定时器,用于红外线解码 */ void Infrared_Time6_Init(void) { RCC->APB1ENR|=14; RCC->APB1RSTR|=14; RCC->APB1RSTR&=~(14); TIM6->PSC=72-1; //预分频器 TIM6->ARR=65535; //重装载寄存器 TIM6->CR1|=17; //开启缓存功能 //TIMx->CR1|=10; //开启定时器 } /* 函数功能: 测量高电平持续的时间 */ u32 Infrared_GetTime_H(void) { TIM6->CNT=0; TIM6->CR1|=10; //开启定时器 while(NEC_IR){} //等待高电平结束 TIM6->CR1&=~(10); //关闭定时器 return TIM6->CNT; } /* 函数功能: 测量低电平持续的时间 */ u32 Infrared_GetTime_L(void) { TIM6->CNT=0; TIM6->CR1|=10; //开启定时器 while(!NEC_IR){} //等待低电平结束 TIM6->CR1&=~(10); //关闭定时器 return TIM6->CNT; } /* 函数功能: 外部中断线9_5服务函数 */ void EXTI9_5_IRQHandler(void) { u32 time; u8 i,j,data=0; //清除中断线9上的中断请求 EXTI->PR|=19; time=Infrared_GetTime_L(); //得到低电平时间 if(time7000||time>10000)return; //标准时间: 9000us time=Infrared_GetTime_H(); //得到高电平时间 if(time3000||time>5500)return; //标准时间4500us //正式解码NEC协议 for(i=0;i4;i++) { for(j=0;j8;j++) { time=Infrared_GetTime_L(); //得到低电平时间 if(time400||time>700)return; //标准时间: 560us time=Infrared_GetTime_H(); //得到高电平时间 if(time>1400&&time1800) //数据1 1680us { data>>=1; data|=0x80; } else if(time>400&&time700) //数据0 560us { data>>=1; } else return; } InfraredRecvData[i]=data; //存放解码成功的值 } //解码成功 InfraredRecvState=1; } ``` ## 3.2 主函数.c ```cpp #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include "at24c02.h" #include "W25Q64.h" #include "spi.h" #include "nec_Infrared.h" int main() { LED_Init(); BEEP_Init(); KeyInit(); USARTx_Init(USART1,72,115200); IIC_Init(); W25Q64_Init(); printf("芯片ID号:0x%X\n",W25Q64_ReadID()); Infrared_RecvInit(); while(1) { if(InfraredRecvState) { InfraredRecvState=0; printf("用户码:%d,按键码:%d\n",InfraredRecvData[0],InfraredRecvData[2]); printf("user反码:%d,key反码:%d\n",(~InfraredRecvData[1])&0xFF,(~InfraredRecvData[3])&0xFF); BEEP=!BEEP; LED0=!LED0; } } } ``` # 四、扩展提高 如果上面的NEC的解码思路已经看到,程序已经可以自己编写,就可以试着使用STM32的输入捕获+定时器方式写一版解码代码,既能更加熟悉NEC协议、也可以学习STM32定时器捕获捕获的用法;也可以做一些小东西来锻炼,比如:红外线遥控小车、音乐播放器支持红外线遥控器切歌,电机的开关、灯的开关等等。 搞定协议解码之后,我们下一步就是完成自定义的NEC协议红外线制作,采用STM32模拟一个万能红外线遥控器。 在光谱中波长自760nm至400um的电磁波称为红外线,它是一种不可见光。目前几乎所有的视频和音频设备都可以通过红外遥控的方式进行遥控,比如电视机、空调、影碟机等,都可以见到红外遥控的影子。这种技术应用广泛,相应的应用器件都十分廉价,因此红外遥控是我们日常设备控制的理想方式。
-
## 一、硬件环境介绍 **1. ESP8266 :** 采用安信可的模组,型号是ESP12F ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657114897122870227.png) **2. STM32 :** 采用STM32F103C8T6 **3. 编程软件 :** 采用Keil5 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657114924940776255.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657114946969753568.png) **ESP8266编程调试过程中用到的相关软件下载地址:** ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657114957288456719.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657114967481766360.png) ### 二、ESP8266通信的调试与运行效果 下面几张图是将ESP8266配置成AP+TCP服务器模式,电脑连接ESP8266的热点之后,实现数据通信。通信的效果是,在电脑点击物联网控制系统软件,实现控制开发板上的LED灯和蜂鸣器,开发板上将检测的光敏数据、温度数据、RC522刷卡数据传输到电脑的软件上进行显示。----局域网通信 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657114979782481854.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657114989325831752.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220706/1657114998536884603.png) ## 三、硬件接线与代码技术部分介绍 **硬件连接:** 下面会贴出核心代码,在当前开发板上,ESP8266接在STM32F103C8T6的串口3上。 **代码分为以下几个部分:** (1) STM32程序里的串口接收采用定时器+接收中断的形式接收数据,使用这种方式可以接收不定长度数据,方便接下来与ESP8266进行通信。 (2). ESP8266驱动代码:代码实现了STA+TCP客户端的一键配置函数,AP+TCP服务器的一键配置函数,要配置ESP8266只需要调用对应的函数传入参数即可。 ## 四、核心代码部分 ### 4.1 ESP8266.c代码 ```cpp #include "esp8266.h" u8 ESP8266_IP_ADDR[16]; //255.255.255.255 u8 ESP8266_MAC_ADDR[18]; //硬件地址 /* 函数功能: ESP8266命令发送函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_SendCmd(char *cmd) { u8 i,j; for(i=0;i10;i++) //检测的次数--发送指令的次数 { USARTx_StringSend(USART3,cmd); for(j=0;j100;j++) //等待的时间 { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,"OK")) { return 0; } } } } return 1; } /* 函数功能: ESP8266硬件初始化检测函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_Init(void) { //退出透传模式 USARTx_StringSend(USART3,"+++"); delay_ms(50); return ESP8266_SendCmd("AT\r\n"); } /* 函数功能: 一键配置WIFI为AP+TCP服务器模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) u16 port 创建的服务器端口号 函数返回值: 0表示成功 其他值表示对应错误值 */ u8 ESP8266_AP_TCP_Server_Mode(char *ssid,char *pass,u16 port) { char *p; u8 i; char ESP8266_SendCMD[100]; //组合发送过程中的命令 /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=2\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 设置WIFI的AP模式参数*/ sprintf(ESP8266_SendCMD,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 开启多连接*/ if(ESP8266_SendCmd("AT+CIPMUX=1\r\n"))return 7; /*8. 设置服务器端口号*/ sprintf(ESP8266_SendCMD,"AT+CIPSERVER=1,%d\r\n",port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 查询本地IP地址*/ if(ESP8266_SendCmd("AT+CIFSR\r\n"))return 9; //提取IP地址 p=strstr((char*)USART3_RX_BUFFER,"APIP"); if(p) { p+=6; for(i=0;*p!='"';i++) { ESP8266_IP_ADDR[i]=*p++; } ESP8266_IP_ADDR[i]='\0'; } //提取MAC地址 p=strstr((char*)USART3_RX_BUFFER,"APMAC"); if(p) { p+=7; for(i=0;*p!='"';i++) { ESP8266_MAC_ADDR[i]=*p++; } ESP8266_MAC_ADDR[i]='\0'; } //打印总体信息 USART1_Printf("当前WIFI模式:AP+TCP服务器\n"); USART1_Printf("当前WIFI热点名称:%s\n",ssid); USART1_Printf("当前WIFI热点密码:%s\n",pass); USART1_Printf("当前TCP服务器端口号:%d\n",port); USART1_Printf("当前TCP服务器IP地址:%s\n",ESP8266_IP_ADDR); USART1_Printf("当前TCP服务器MAC地址:%s\n",ESP8266_MAC_ADDR); return 0; } /* 函数功能: TCP服务器模式下的发送函数 发送指令: */ u8 ESP8266_ServerSendData(u8 id,u8 *data,u16 len) { u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d,%d\r\n",id,len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j10;j++) { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n200;n++) { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,"SEND OK")) { return 0; } } } } } } } return 1; } /* 函数功能: 配置WIFI为STA模式+TCP客户端模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) char *p 将要连接的服务器IP地址 u16 port 将要连接的服务器端口号 u8 flag 1表示开启透传模式 0表示关闭透传模式 函数返回值:0表示成功 其他值表示对应的错误 */ u8 ESP8266_STA_TCP_Client_Mode(char *ssid,char *pass,char *ip,u16 port,u8 flag) { char ESP8266_SendCMD[100]; //组合发送过程中的命令 //退出透传模式 //USARTx_StringSend(USART3,"+++"); //delay_ms(50); /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=1\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 配置将要连接的WIFI热点信息*/ sprintf(ESP8266_SendCMD,"AT+CWJAP=\"%s\",\"%s\"\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 设置单连接*/ if(ESP8266_SendCmd("AT+CIPMUX=0\r\n"))return 7; /*8. 配置要连接的TCP服务器信息*/ sprintf(ESP8266_SendCMD,"AT+CIPSTART=\"TCP\",\"%s\",%d\r\n",ip,port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 开启透传模式*/ if(flag) { if(ESP8266_SendCmd("AT+CIPMODE=1\r\n"))return 9; //开启 if(ESP8266_SendCmd("AT+CIPSEND\r\n"))return 10; //开始透传 if(!(strstr((char*)USART3_RX_BUFFER,">"))) { return 11; } //如果想要退出发送: "+++" } //打印总体信息 USART1_Printf("当前WIFI模式:STA+TCP客户端\n"); USART1_Printf("当前连接的WIFI热点名称:%s\n",ssid); USART1_Printf("当前连接的WIFI热点密码:%s\n",pass); USART1_Printf("当前连接的TCP服务器端口号:%d\n",port); USART1_Printf("当前连接的TCP服务器IP地址:%s\n",ip); return 0; } /* 函数功能: TCP客户端模式下的发送函数 发送指令: */ u8 ESP8266_ClientSendData(u8 *data,u16 len) { u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d\r\n",len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j10;j++) { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n200;n++) { delay_ms(50); if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART3_RX_FLAG=0; USART3_RX_CNT=0; if(strstr((char*)USART3_RX_BUFFER,"SEND OK")) { return 0; } } } } } } } return 1; } ``` ### **4.2 ESP8266.h** ```cpp #ifndef _ESP8266_H #define _ESP8266_H #include "stm32f10x.h" #include "usart.h" #include "delay.h" //函数声明 u8 ESP8266_Init(void); u8 ESP8266_SendCmd(char *cmd); u8 ESP8266_AP_TCP_Server_Mode(char *ssid,char *pass,u16 port); u8 ESP8266_ServerSendData(u8 id,u8 *data,u16 len); u8 ESP8266_STA_TCP_Client_Mode(char *ssid,char *pass,char *ip,u16 port,u8 flag); u8 ESP8266_ClientSendData(u8 *data,u16 len); #endif ``` ### 4.3 串口部分代码 ```cpp /* 函数功能: 串口1的初始化 硬件连接: PA9(TX) 和 PA10(RX) */ void USART1_Init(u32 baud) { /*1. 开时钟*/ RCC->APB2ENR|=114; //USART1时钟 RCC->APB2ENR|=12; //PA RCC->APB2RSTR|=114; //开启复位时钟 RCC->APB2RSTR&=~(114);//停止复位 /*2. 配置GPIO口的模式*/ GPIOA->CRH&=0xFFFFF00F; GPIOA->CRH|=0x000008B0; /*3. 配置波特率*/ USART1->BRR=72000000/baud; /*4. 配置核心寄存器*/ USART1->CR1|=15; //开启接收中断 STM32_SetPriority(USART1_IRQn,1,1); //设置中断优先级 USART1->CR1|=12; //开启接收 USART1->CR1|=13; //开启发送 USART1->CR1|=113;//开启串口功能 } /* 函数功能: 串口3的初始化 硬件连接: PB10(TX) 和 PB11(RX) */ void USART3_Init(u32 baud) { /*1. 开时钟*/ RCC->APB1ENR|=118; //USART3时钟 RCC->APB2ENR|=13; //PB RCC->APB1RSTR|=118; //开启复位时钟 RCC->APB1RSTR&=~(118);//停止复位 /*2. 配置GPIO口的模式*/ GPIOB->CRH&=0xFFFF00FF; GPIOB->CRH|=0x00008B00; /*3. 配置波特率*/ USART3->BRR=36000000/baud; /*4. 配置核心寄存器*/ USART3->CR1|=15; //开启接收中断 STM32_SetPriority(USART3_IRQn,1,1); //设置中断优先级 USART3->CR1|=12; //开启接收 USART3->CR1|=13; //开启发送 USART3->CR1|=113;//开启串口功能 } u8 USART3_RX_BUFFER[USART3_RX_LENGTH]; //保存接收数据的缓冲区 u32 USART3_RX_CNT=0; //当前接收到的数据长度 u8 USART3_RX_FLAG=0; //1表示数据接收完毕 0表示没有接收完毕 //串口3的中断服务函数 void USART3_IRQHandler(void) { u8 data; //接收中断 if(USART3->SR&15) { TIM3->CNT=0; //清除计数器 TIM3->CR1|=10; //开启定时器3 data=USART3->DR; //读取串口数据 // if(USART3_RX_FLAG==0) //判断上一次的数据是否已经处理完毕 { //判断是否可以继续接收 if(USART3_RX_CNT } else //不能接收,超出存储范围,强制表示接收完毕 { USART3_RX_FLAG=1; } } } } /* 函数功能: 字符串发送 */ void USARTx_StringSend(USART_TypeDef *USARTx,char *str) { while(*str!='\0') { USARTx->DR=*str++; while(!(USARTx->SR&17)){} } } /* 函数功能: 数据发送 */ void USARTx_DataSend(USART_TypeDef *USARTx,u8 *data,u32 len) { u32 i; for(i=0;iDR=*data++; while(!(USARTx->SR&17)){} } } /* 函数功能: 格式化打印函数 */ char USART1_PRINTF_BUFF[1024]; void USART1_Printf(char *fmt,...) { va_list ap; /*1. 初始化形参列表*/ va_start(ap,fmt); /*2. 提取可变形参数据*/ vsprintf(USART1_PRINTF_BUFF,fmt,ap); /*3. 结束,释放空间*/ va_end(ap); /*4. 输出数据到串口1*/ USARTx_StringSend(USART1,USART1_PRINTF_BUFF); //USART1_Printf("%d%s",123,454656); //int data=va_arg(ap,int); } ``` ### 4.4 定时器部分代码 ```cpp /* 函数功能: 配置定时器3 函数参数: psc 预分频器 arr重装载值 */ void TIMER3_Init(u16 psc,u16 arr) { /*1. 开时钟*/ RCC->APB1ENR|=11; //开启定时器3的时钟 RCC->APB1RSTR|=11;//开启定时器3复位时钟 RCC->APB1RSTR&=~(11);//关闭定时器3复位时钟 /*2. 配置核心寄存器*/ TIM3->PSC=psc-1; TIM3->ARR=arr; TIM3->DIER|=10; //开启更新中断 STM32_SetPriority(TIM3_IRQn,1,1); //设置中断优先级 // TIM3->CR1|=10; //开启定时器3 } /* 函数功能: 定时器3中断服务函数 */ void TIM3_IRQHandler(void) { if(TIM3->SR&10) { TIM3->SR&=~(10); USART3_RX_FLAG=1; //表示接收完毕 TIM3->CR1&=~(10); //关闭定时器3 } } ``` ### 4.5 主函数调用部分(STA+TCP客户端)示例 ```cpp int main() { u8 key,cnt=0; LED_Init(); BEEP_Init(); KEY_Init(); USART1_Init(115200); USART3_Init(115200);//串口-WIFI TIMER3_Init(72,20000); //超时时间20ms USART1_Printf("正在初始化WIFI请稍等.\n"); if(ESP8266_Init()) { USART1_Printf("ESP8266硬件检测错误.\n"); } else { USART1_Printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode("ChinaNet-wbyw","12345678","192.168.101.6",8088,1)); } while(1) { if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART1_Printf("%s",USART3_RX_BUFFER); USART3_RX_CNT=0; USART3_RX_FLAG=0; } key=KEY_Scan(0); if(key==2) { USARTx_StringSend(USART3,"AT+GMR\r\n"); //查看版本信息 } else if(key==3) { USARTx_StringSend(USART3,"12345ABCD"); } else if(key==4) //退出透传模式 { USARTx_StringSend(USART3,"+++"); } else if(key==5) //发送AT { USARTx_StringSend(USART3,"AT+CIPSTATUS\r\n"); //查看状态信息 } } } ``` ### 4.6 主函数调用部分(AP+TCP服务器)示例 ```cpp int main() { u8 key; LED_Init(); BEEP_Init(); KEY_Init(); USART1_Init(115200); USART3_Init(115200);//串口-WIFI TIMER3_Init(72,20000); //超时时间20ms USART1_Printf("正在初始化WIFI请稍等.\n"); //初始化WIFI硬件 if(ESP8266_Init())USART1_Printf("WIFI硬件错误.\n"); else { //配置WIFI的模式 USART1_Printf("WIFI配置状态:%d\n",ESP8266_AP_TCP_Server_Mode("esp8266_666","12345678",8088)); } while(1) { if(USART3_RX_FLAG) { USART3_RX_BUFFER[USART3_RX_CNT]='\0'; USART1_Printf("%s",USART3_RX_BUFFER); USART3_RX_CNT=0; USART3_RX_FLAG=0; } key=KEY_Scan(0); if(key==2) { ESP8266_ServerSendData(0,(u8*)"1234567890",10); } else if(key==3) { ESP8266_ServerSendData(0,(u8*)"abcd",4); } } } ```
推荐直播
-
手把手教你实现mini版TinyVue组件库
2024/04/17 周三 16:30-18:00
阿健 华为云前端开发DTSE 技术布道师
在前端Web开发过程中,跨版本兼容性问题是一个普遍存在的挑战。为了解决这些痛点,OpenTiny推出跨端、跨框架、跨版本组件库TinyVue。本期直播聚焦于华为云的前端开源组件库TinyVue,通过mini版TinyVue的代码实践与大家共同深入解读Vue2/Vue3不同版本间的差异。这对于提升用户体验,减低维护成本,提升开发者技术洞察有重要意义。
回顾中 -
如何快速入驻O3使能伙伴服务作业平台
2024/04/18 周四 16:00-16:40
红喜 O3伙伴服务工作台技术总架构师
本期邀请O3伙伴服务工作台技术总架构师,讲解O3伙伴服务工作台的设计理念,及演示工作台关键能力与价值点,带你2步快速入驻工作台。O3伙伴服务工作台,具备在线Online、开放Open、协同Orchestration的特征,作为伙伴服务的统一入口,支持伙伴以租户方式入驻,涵盖伙伴工程师、管理者等多角色,是一个以伙伴服务领域全旅程作业为中心,整合华为服务各专业领域能力,开放共享的一站式作业平台。
去报名
热门标签