【Nano求包養網Pi NEO2試用體驗】智能家庭把持器–結項
智能家庭把持器這個項目忙活了快要一個多月,我的任務重點放在了圖像傳輸和音頻傳輸上,而把持方面我這沒有做太多任務,由於之前做Orangepi包養故事 Zero的時辰曾經做過了良多這些義務,參考以前的帖子,包含GPIO的把持、LCD5110的把持,I2C讀取MPU6050等等,這里不再多做重復的任務,將重點放在焦點上。
後面幾個帖子曾經先容了應用UVC攝像頭停止圖像采集并停止jpeg圖像轉換的任務,后面我又做了一些音頻方面的任務,在linux下停止音頻采集和播放我應用的是基于oss框架的法式design,alsa框架略微費事一點。停止聲響播放和錄制實在很簡略,和其他的裝備應用方法一樣就是翻開、讀寫和封閉裝備就行了。翻開的裝備是dsp裝備,翻開之后設置采集音頻的參數包含采樣率、量化位數、聲道數。然后挪用read函數就能停止停止灌音了,取得的數據就是原始的PCM數據也就是原始音頻數據,這些數據可以直接wr包養網推薦ite到dsp裝備長進行播放。上面就是應用dsp裝備停止數據采集和播放的法式段:
#define LENGTH 8 //灌音時光,秒
#define RATE 8000 //采樣頻率
#define SIZE 8 //量化位數
#define CHANNELS 包養平台 1 //聲道數量
#define RSIZE 8192 //buf的鉅細
static int recoder_fd = -1;
int OpenRecoder()
{
recoder_fd = open(“/dev/dsp”, O_RDWR);
if (recoder_fd {
perror(“Cannot open /dev/dsp device”);
return 1;
}
int arg = SIZE;
if (ioctl(recoder_fd, SOUND_PCM_WRITE_BITS, &arg) == -1) //設置量化位數
{
perror(“Cannot set SOUND_PCM_WRITE_BITS “);
return 1;
包養網比較 }
arg = CHANNELS;
if (ioctl(recoder_fd, SOUND_PCM_WRITE_CHANNELS, &arg) == -1) //設置聲道數
{
包養站長perror(“Cannot set SOUND_PCM_WRITE_CHANNELS”);
return 1;
}
arg = RATE;
if (ioctl(recoder_fd, SOUND_PCM_WRITE_RATE, &arg) == -1) //設置采樣率
{
perror(“Cannot set SOUND_PCM_WRITE_WRITE”);
return 1;
}
}
void CloseRecoder()
{
if(recoder_fd > 0)
close(recoder_fd);
}
int Recording(unsigned char *buff,int len)
{
if(recoder_fd > 0)
return read(recoder_fd, buff, len);
else
return 0;
}
int Playing(unsigned char *buff,int len)
{
if(recoder_fd > 0)
write(recoder_fd, buff, len);
}
復制代碼
采集到的數據是不克不及直接保留成文件在Windows等體系辨認,需求停止格局封裝,這里停止的是wav格局的封裝包養意思,wav的格局可以參考:http://blog.csdn.net/mlkiller/article/details/12567139,wav的數據格局表如下:
偏移地址
鉅細
字節
數據塊
類型
內在的事務
00H~03H
4
4字符
資本交流文件標志(RIFF)
04H~07H
4
長整數
從下個地址開端到文件尾的總字節數
08H~0BH
4
4字符
WAV文件標志(WAVE)
0CH~0FH
4
4字符
波形格局標志(fmt ),最后一位空格。
10H~13H
4
整數
過濾字包養違法節(普通為00000010H)
14H~15H
2
整數
格局品種(值為1時,表現數據為線性PCM編碼)
16H~17H
2
整數
通道數,單聲道為1,雙聲道為2
18H~1BH
4
長整數
采樣頻率
1CH~1FH
4
長整數
波形數據傳輸速度(每秒均勻字節數)
20H~21H
2
整數
DATA數據塊長度,字節。22H~23H
2
整數
PCM位寬
24H~27H44字符“fact”,該部門一下是可選部門,即能夠有,能夠沒有,普通到WAV文件由某些軟件轉換而成時,包括這部門。
28H~2BH4長整數
size,數值為4
wav是我見過的最簡略的文件格局,封裝wav的函數也很簡略:
struct WAV_HEADER wavehead =
{
.RIFF = { R , I , F , F },
.WAVE = { W , A , V , E },
.fmt = { f , m , t , },
.appendinfo = 16,
.format = 1,
.desc = { d , a , t , a },
};
//保留采樣率8000,采樣深度8位,1聲道的數據
void SavePCM2WAV(unsigned char *buff,int len,char *file)
{
system(“rm a.wav”);
system(“touch a.wav”);
wavehead.lenth = len + sizeof(struct WAV_HEADER) – 8;
wavehead.channel = 1;
wavehead.freq =包養甜心網 8000;
wavehead.rate = 8000*8/8;
wavehead.sampling = 1;
wavehead.datalenth = len;
wavehead.depth = 8;
int fd = open(“a.wav”,O_RDWR);
if(fd > 0)
{
int ret = write(fd,(unsigned char*)(&wavehead),sizeof(struct WAV_HEADER));
if(ret != sizeof(struct WAV_HEADER))
{
printf(“write header errorrn”);
system(“rm a.wav”);
return;
}
ret = write(fd,buff,len);
if(ret != len)
{
printf(“write data errorrn”);
system(“rm a.wav”);
包養俱樂部 return;
}
close(fd);
}
}
復制代碼
wav格局的文件固然簡略可是文件很年夜,晦氣于收集傳輸,這里可以應用號令將wav文件轉成成mp3文件,文件鉅細會被緊縮良多,應用的是lame庫,可以本身裝置或許編譯lame源碼:
lame sample.wav sample.mp3
獲得的mp3文件就能停止收集傳輸了。
圖像和音頻基礎上搞定了上面就是做重要的法式部門了,包含收集部門和硬件電路部門,由于NanoPi NEO2沒有接出mic和揚聲器我需求本身design一個mic和揚聲器縮小的電路,我預備本身做一個PCB板,包括mic電路和揚聲器縮小電路,可是這里我犯了一個極為***的過錯,我把心愛的NanoPi NEO2當成了orangepi zero了,我做板的時辰都是依照orangepi zero的尺寸和電路做的,我真的是。。。看錯電路拿錯板子。不外我想法式都是Linux通用的,哪里跑都是一樣的,我先用這個orangepi zero板子試驗了就,由於pcb打板要很長的時光,我沒時光等候了,此刻就收回告終項貼了,其實負疚。電路板的圖片如下:
USB擴大電路
等我的NanoPi NEO2的板子做好了得等一個多禮拜,到時辰補一個帖子吧。重要仍是創作的分送朋友經過歷程,硬件上年夜同小異,不外NanoPi NEO2的玲瓏身體簡直加了良多分。我很愛好,如果自帶wifi的話就好了。
電路弄好了上面就是法式了。辦事器應用的是公司的不開源的一個小型的辦事器,并不是很牛的辦事器,而是由於貿易緣由沒有開源,所以只能應用不克不及研討了。把持器的焦點收集部門的代碼是一個狀況機法式:
void* network_thread(void *arg)
{
int i;
printf(“network_thread startrn”);
while(1)
{
if(network_stm == 0)
{
if(NetworkInit() == 0)
{
printf(“Network Initialize successfullyrn”);
network_stm = 1;
}
else
{
printf(“Network Initialize failedrn”);
usleep(5000000); //收集初始化掉敗的話延時一會再重連
包養網VIP }
}
else if(network_stm == 1)
{
sprintf(login_string,”lock,%s,”,key);
printf(“login_string:%srn”,login_string);
// write(sock_client,str,strlen(str));
SendNetFrame(login_string,strlen(login_string));
int len = read(sock_client,read_buff,1024);
// printf(“%drn”,len);
if(len > 0)
{
read_buff[len] = 0;
printf(“%srn”,read_buff);
if(strcmp(read_buff,”success”) == 0)
{
printf(“login successfullyrn”);
network_stm = 2;
}
else
{
printf(“login failedrn”);
close(sock_client);
network_stm = 0;
包養一個月價錢 usleep(5000000); //登錄掉敗,封閉socket然后回到狀況0
}
}
else if(len == 0)
{
printf(“connection closed by serverrn”);
network_stm = 0;
包養網dcard }
}
else if(network_stm == 2)
{
int len = read(sock_client,read_buff,1024);
if(len > 0)
{
// printf(“read_buff len:%d,”,len);
// read_buff[len] = 0;
// printf(“%srn”,read_buff);
for(i=0;i {
InputData(read_buff[i]);
if(IsDataReady == 1)
{
IsDataReady = 0;
input_frame[input_frame_len] = 0; //最后補0,避免停止字符串比擬的時辰數組越界
printf(“frame is ready,length:%drn”,input_frame_len);
甜心花園
if(input_frame[0] == p ) //假如收到的是說話數據
{
printf(“frame type:PCM datarn”);
OpenRecoder();
Playing(input_frame+1,input_frame_len-1);
包養app CloseRecoder();
包養網站 }
else if(input_frame[0] == c ) //假如收到的是把持號令
{
printf(“frame type:command:%srn”,input_frame);
秦家的人點了點頭,對此沒有發表任何意見,然後抱拳道:“既然消息已經帶進來,下面的任務也完成了,那我就走了。 if(strstr(input_frame,”get picture”) != 0)
{
FLAG_GET_AND_SEND_PIC = 1;
}
else if(strstr(input_frame,”open lock”) != 0)
{
FLAG_OPENDOOR = 1;
}
else if(strstr(input_frame,”stream_on_off”) != 0)
{
FLAG_GET_VIDEO_STREAM = FLAG_GET_VIDEO_STREAM?0:1;
}
}
}
}
}
else if(len == 0)
{
printf(“connection closed by serverrn”);
network_stm = 0;
}
}
usleep(1000);
}
}
復制代碼
用于解析接受到的收集數據,這是法式的焦點,其他的攝像頭和音頻的部門後面都有法式不再說了。當收到來自手機端發來的號令的時辰收集線程就會處置接受到的號令判定是攝影號令仍是來自手機上的音頻數據等等。安卓手機上的法式我也是簡略的做了一個,我不是專門做安卓的所以做的很粗拙,只是一個年夜致的構造:
還可以停止雙邊說話通訊,通訊方法和微信相似,按下措辭松開闢送,安卓這塊的代碼寫的很渣滓,僅供參考:
public class MainActivity extends AppCompatActivity {
static Socket socket = null;
OutputStream ou;
InputStream in;
LinearLayout main_linear_layout;
ImageView image_view;
ImageView big_image_view;
Button btn_openlock;
Button btn_getpic;
Button btn_setup;
Button btn_record;
Handler handler = new Handler();
byte send_buff[]; //發送數據緩沖區
int send_buff_len; //發送數據緩沖區長度
byte raw_data_buff[] = new byte[1024 * 1024]; //讀取socket獲得的原始數據
byte frame_buff[] = new byte[1024*1024]; //原始數據解析獲得的有用數據幀
boolean IsFrameReady = false;
int frame_buffLen;
int state = 0;
int count = 0;
Bitmap bitmap;
byte[] img_buf = new byte[1024 * 10台灣包養網24]; //接受到的jpeg圖片數據
byte[] mp3_buf = new byte[1024 * 1024]; //接受到的mp3數據
private NotificationManager notificationManager;
Thread RecordThread = null;
AudioRecord audioRecord = null;
byte[] RecordBuffer = new byte[8000 * 20];
int RecordBufferLen = 0;
int audioSource = MediaRecorder.AudioSource.MIC;
//設置音頻采樣率,44100是今朝的尺度,可是某些裝備依然支撐22050,16000,11025
int sampleRateInHz = 8000;
//設置音頻的錄制的聲道CHANNEL_IN_STEREO為雙聲道,CHANNEL_CONFIGURATION_MONO為單聲道
int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
//音頻數據格局:PCM 16位每個樣本。包管裝備支撐。PCM 8位每個樣本。紛歧定能獲得裝備支撐。
int audioFormat = AudioFormat.ENCODING_PCM_8BIT;
int bufferSizeInBytes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_main);
main_linear_layout = (LinearLayout)findViewById(R.id.main_linear_layout);
image_view = (ImageView)findViewById(R.id.image_view);
image_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
big_image_view.setVisibility(View.VISIBLE);
main_linear_layout.setVisibility(View.GONE);
}
});
big_image_view = (ImageView)findViewById(R.id.big_image_view);
big_image_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
big_image_view.setVisibility台灣包養(View.GONE);
main_linear_layout.setVisibility(View.VISIBLE);
}
});
btn_openlock = (Button)findViewById(R.id.btn_openlock);
btn_openlock.setOnClickListener(btn_openlock_OnClickListener);
btn_setup = (Button)findViewById(R.id.btn_setup);
btn_setup.setOnClickListener(btn_setup_OnClickListener);
btn_getpic = (Button)findViewById(R.id.btn_getpic);
btn_getpic.setOnClickListener(btn_getpic_OnClickListener);
btn_record = (Button)findViewById(R.id.btn_record);
// btn_record.setOnClickListener(btn_record_OnClickListener);
btn_record.setOnTouchListener(btn_record_OnTouchListener);
ReadThread.start();
// 第一個步驟:經由過程getSystemService()方式獲得NotificationManager對象;
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//靜態請求權限,高版本的安卓體系需求靜態請求權限才行,只修正manifest文件沒用
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.RECORD_AUDIO},0);
}
//靜態請求權限回調函數
public void onRequestPermissionsResult(int requestCode,String permissions[], int[] grantResults) {
switch (requestCode) {
case 0: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i(“tangquan”, “permission RECORD_AUDIO request success”);
} else {
Log.i(“tangquan”, “permission RECORD_AUDIO request failed”);
}
return;
}
}
}
//幀把持輸出一個字節的數據
void InputData(byte data)
{
包養網評價 if(state == 0)
{
if(IsFrameReady == false)
{
if(data == T )
{
state = 1;
count = 0;
}
}
}
else if(state == 1)
{
if(data == \ )
{
state = 2;
}
else if(data == Q )
{
state = 0;
IsFrameReady = true;
frame_buffLen = count;
}
else
{
frame_buff[count++] = data;
}
}
else if(state == 2)
{
state = 1;
frame_buff[count++] = data;
}
}
void SendFrame(char type,byte[] buff)
{
byte frame[] = new byte[buff.length * 2];
int i,j = 0; //j用于計數frame的長度
frame[j++] = T
frame[j++] = (byte)type;
for(i=0;i {
包養平台 if(buff[i] == T || buff[i] == Q || buff[i] == \ )
{
frame[j] = \ //拔出一個本義字符
j++;
}
frame[j] = buff[i];
j++;
}
frame[j] = Q
j++;
SocketSendBytes(frame,j);
}
void SendFrame(char type,byte[] buff,int len)
{
byte frame[] = new byte[buff.length * 2];
int i,j = 0; //j用于計數frame的長度
frame[j++] = T
frame[j++] = (byte)type;
for(i=0;i {
if(buff[i] == T || buff[i] == Q || buff[i] == \ )
{
frame[j] = \ //拔出一個本義字符
j++;
}
frame[j] = buff[i];
j++;
}
frame[j] = Q
j++;
SocketSendBytes(frame,j);
}
Thread ReadThread = new Thread()
{
public void run()
{
socket = new Socket();
try
{
// socket.connect(new InetSocketAddress(“192.168.1.106”,5000),1000);
socket.connect(new InetSocketAddress(“103.76.85.83”,5000),1000);
ou = socket.getOutputStream();
in = socket.getInputStream();
SocketSendStr(“phone,AAAAEqo7V7dNvVP8,”);//發送賬號和password
int len = in.read(raw_data_buff,0,100);//read是梗阻式的,用SocketChannel可以不梗阻
if(len > 0)
{
//需求停止截斷,否則直接把result轉成字符串那么這個字符串的長度是result的長度
String res_str = new String(Arrays.copyOfRange(raw_data_buff, 0, len));
if(res_str.equals(“success”))
{
PostToast(“銜接勝利”);
}
else
{
PostToast(“賬號過錯”);
socket.close();//封閉socket銜接
socket = null;
return;
}
}
}
catch(Exception e)
{
socket = null;
PostToast(“銜接掉敗”);
return;
}
while(socket != null)
{
try {
int len = in.read(raw_data_buff,0,raw_data_buff.length);
if(len > 0)
{
// PostToast(“len:” + String.valueOf(len));
for(int i=0;i {
InputData(raw_data_buff[i]);
//能否接收到一幀數據
if(IsFrameReady)
{
IsFrameReady = false;
// PostToast(“get one frame,len:”+String.valueOf(frame_buffLen)+”,type:”+String.valueOf((char)frame_buff[0]));
Log.i(“tangquan”,”get one frame,len:”+String.valueOf(frame_buffLen)+”,type:”+String.valueOf((char)frame_buff[0]));
if((char)frame_buff[0] == j )
{
// showNotification(“tangquan”,”content”,”contenttext”,0,0);
for(int j=1;j {
sd包養 img_buf[j – 1] = frame_buff[j];
}
handler.post(new Runnable()
{
@Override
public void包養犯法嗎 run(){
bitmap = BitmapFactory.decodeByteArray(img_buf,0,frame_buffLen-1);
image_view.setImageBitmap(bitmap);
big_image_view.setImageBitmap(bitmap);包養平台
}
});
}
else if((char)frame_buff[0] == m )
{
Log.i(“tangquan”, “get mp3 data”);
for(int j=1;j mp3_buf[j – 1] = frame_buff[j];
String filename = “a.mp3”;
File file = new File(MainActivity.this.getFilesDir(),filename);
FileOutputStream outputStream;
try{
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(mp3_buf);
outputStream.close();
} catch(Exception e) {
e.printStackTrace();
}
Log.i(“tangquan”, MainActivity.this.getFilesDir()+”/”+filename);
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(MainActivity.this.getFilesDir()+”/”+filename);
mp.prepare();
mp.start();
第二次拒絕,直接又清晰,就像是一記耳光,讓她猝不及防,心碎,淚水控制不住的從眼眶裡流了下來。 }
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
try {
sleep(10);
} catch (Exception ignored) {}
}
}
};
View.OnClickListener btn_openlock_OnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
PostToast(“btn_openlock_OnClickListener”);
// showNotification(“tangquan”,”content”,”contenttext”,0,0);
SendFrame( c ,”open lock”.getBytes());
}
};
View.OnClickListener btn_getpic_OnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
SendFrame( c ,”get picture”.getBytes());
}
};
View.OnClic包養甜心網kListener btn_record_OnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(“tangquan”, “btn_setup press”);
}
};
boolean isRecording = false;
View.OnTouchListener btn_record_OnTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, Moti他從小就和母親一起生活,沒有其他家人或親戚。onEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
Log.i(“tangquan”, “btn_record pressed”);
RecordThread = new Thread()
{
public void run()
{
包養違法 // Create a new AudioRecord object to record the audio.
// 取得知足前提的最小緩沖區鉅細
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
Log.i(“tangquan”, “bufferSizeInBytes:” + String.valueOf(bufferSizeInBytes));
//包養合約 創立AudioReco短期包養rd對象
audioRecord = new AudioRecord(audioSource, sampleRateInHz,channelConfig, audioFormat, bufferSizeInBytes);
if(audioRecord != null)
{
audioRecord.startRecording();
isRecording = true;
RecordBufferLen = 0;
while (isRecording) {
RecordBufferLen台灣包養網 += audioRecord.read(RecordBuffer, RecordBufferLen, bufferSizeInBytes);
Log.i(“tangquan”, String.valueOf(RecordBufferLen));
}
audioRecord.stop();
audioRecord.release();// 開釋資本
audioRecord = null;
}
else
{
Log.i(“tangquan”, “create AudioRecord failed”);
}
}
};
RecordThread.start();
}
else if(event.getAction() == MotionEvent.ACTION_UP)
{
Log.i(“tangquan”, “btn_record released”);
if(RecordThread != null && audioRecord != null)
{
isRecording = false;
new Thread()
{
public void run()
包養網ppt {
// AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat,bufferSizeInBytes, AudioTrack.MODE_STREAM);
// audioTrack.play(); //放音
// try {
// audioTrack.write(RecordBuffer, 0, RecordBufferLen);
// } catch (Exception e) {
// Log.e(“AudioTrack”, “Playback Failed”);
// }
SendFrame( p ,RecordBuffer,RecordBufferLen);
}
}.start();
}
}
return false;
}
};
View.OnClickListener btn_setup_OnClickListener = new View.OnClickListener() {
@Override
public包養網評價 void onClick(View v) {
}
};
void SocketSendBytes(byte[] buff,int len) {
send_buff = buff;
send_buff_len = len;
// Log.i(“tangquan”, String.valueOf(send_buff_len));
new Thread()
{
@Override
public void run() {
if (socket == null)
return;
try {
ou.write(send_buff,0,send_buff_len);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
void SocketSendStr(String str) {
send_buff = str.getBytes();
new Thread()
{
@Overrid包養網dcarde
public void run() {
if (socket == null)
return;
try {
ou.write(send_buff);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
//在線程中挪用Toast會犯錯,不了解詳細緣由,可是網上說用 handler.post可以做
//所以弄了一個類和一個函數,類是完成了Runnable接口,同時添加了一個字符串變量
class MyRunnable implements Runnable
{
String Message;
public MyRunnable(String msg)
{
Message = msg;
}
public void run(){
}
}
void PostToast(String msg)
{
handler.post(new MyRunnable(msg)
{
@Override
public void run(){
Toast.makeText(MainActivity.this, Message, Toast.LENGTH_SHORT).show();
}
});
}
// 第二步:對Notification的一些屬性停止設置好比:內在的事務,圖標,題目,響應notification的舉措停止處置等等;
public void showNotification(String tickerText, String contentTitle, String contentText, int iconId, int notiId) {
Notification myNotify = new Notification();
myNotify.icon = R.drawable.lock;
myNotify.tickerText = “TickerText:您有新短新聞,請留意查收!”;
myNotify.when = System.currentTimeMillis();
// myNotify.flags = Notification.FLAG_NO_CLEAR;// 不克不及夠主動肅清
// rv.setTextViewText(R.id.text_content, “hello wrold!”);
// myNotify.contentView = rv;
Intent intent = new Intent(Intent.ACTION_MAIN);
PendingIntent contentIntent = PendingIntent.getActivity(this, 1,intent, FLAG_UPDATE_CURRENT);
myNotify.contentIntent = contentIntent;
notificationManager.notify(2, myNotify);
}
// 6步:應用notificationManager對象的cancelAll()方式撤消
public void clearNoti(View v) {
notificationManager包養網站.cancelAll();// 肅清一切
}
}
復制代碼
至此家庭把持器曾經做完了,可以停止家庭攝影和家庭說話對講,獨一的缺乏時PCB做錯了,這點真的很煩惱,PCB制版費還真未便宜,誒。這一個月的應用我對NanoPi有一些小感慨,不得不說NanoPi的唱工真的很好,說真話我不是可以夸年夜NanoPi抬高Orangepi,NanoPi的唱工比Orangepi好良多,我看了一下板子的布線,NanoPi以這般小的面積布出如許的板子其實是兇猛,真的很愛好NanoPi NEO2,太小了太強盛了,盼望后面可以或許應用NEO2做更多風趣的工具,感謝電子發熱友供給的機遇!
• 【NanoPi NEO2試用體驗】Nano Pi NEO2 試專心得+ROS搭建 5889