经过前面几篇文章的铺垫,完成了C语言接口层的GPIO、外部中断、串口、delay等接口,现在可以正式进入C++驱动层的文章。当然C语言接口层的还远没有完成,在以后的文章中还会继续更新。
本文将会介绍一个C++驱动层中非常重要的两个类,outputStream和inputStream,作为输入输出流的基类,为单片机的数据输出提供了统一的接口。在以后的文章中会介绍到的硬件串口类 HardwareUART类和移植的Adafruit的图形类Adafruit_GFX类等都是继承这个类的派生类。这两个类主要参考了Arduino的Print类和Stream类,并进行了修改和扩展。可以满足多种风格的输入输出。

一、从Arduino的串口输入输出函数分析输入输出类:

有使用过Arduino的大佬应该对Arduino的输入输出函数很熟悉,先举几个栗子

1.Arduino的串口输入输出函数

Serial.print("hello world");
Serial.println("hello wrold");
char* str="hello world";
Serial.write(str, strlen(str));char c;
if(Serial.available())c=Serial.read();
......

2.ESP32/8266 TCP服务器或客户端的输入输出函数

client.print("hello world");
client.println("hello wrold");
char* str="hello world";
client.write(str, strlen(str));char c;
if(client.available())c=client.read();

3.Adafruit的OLED库打印字符

display.println("hello wrold");
display.display();

这三个栗子分别操作不同的硬件,而调用的函数却都是一样的print、println、write等。我们再来看看这几个对象的所对应的类:
1.Arduino的串口Serial:
Arduino的串口Serial是类HardwareSerial的实例化对象,这个类的定义和声明可以在arduino安装目录下的hardware\arduino\avr\cores\arduino中找到

class HardwareSerial : public Stream

2.服务器或客户端的输入输出函数
以客户端为例,client对象是类Client的实例化对象,这个类的声明也可以在arduino安装目录下的hardware\arduino\avr\cores\arduino中找到

class Client : public Stream

3.Adafruit的OLED
oled对象是类Adafruit_SSD1306的实例化对象,Adafruit_SSD1306继承自类Adafruit_GFX,我们来看看Adafruit_GFX的声明

class Adafruit_GFX : public Print

通过上面三个例子,我们发现他们对应的类有两个继承自Stream类,一个继承自Print类,我们再来看看这两个类:

class Stream : public Print
{protected:unsigned long _timeout;      // number of milliseconds to wait for the next char before aborting timed readunsigned long _startMillis;  // used for timeout measurementint timedRead();    // read stream with timeoutint timedPeek();    // peek stream with timeoutint peekNextDigit(LookaheadMode lookahead, bool detectDecimal); // returns the next numeric digit in the stream or -1 if timeoutpublic:virtual int available() = 0;virtual int read() = 0;virtual int peek() = 0;Stream() {_timeout=1000;}// parsing methodsvoid setTimeout(unsigned long timeout);  // sets maximum milliseconds to wait for stream data, default is 1 secondunsigned long getTimeout(void) { return _timeout; }bool find(char *target);   // reads data from the stream until the target string is foundbool find(uint8_t *target) { return find ((char *)target); }// returns true if target string is found, false if timed out (see setTimeout)bool find(char *target, size_t length);   // reads data from the stream until the target string of given length is foundbool find(uint8_t *target, size_t length) { return find ((char *)target, length); }// returns true if target string is found, false if timed outbool find(char target) { return find (&target, 1); }bool findUntil(char *target, char *terminator);   // as find but search ends if the terminator string is foundbool findUntil(uint8_t *target, char *terminator) { return findUntil((char *)target, terminator); }bool findUntil(char *target, size_t targetLen, char *terminate, size_t termLen);   // as above but search ends if the terminate string is foundbool findUntil(uint8_t *target, size_t targetLen, char *terminate, size_t termLen) {return findUntil((char *)target, targetLen, terminate, termLen); }long parseInt(LookaheadMode lookahead = SKIP_ALL, char ignore = NO_IGNORE_CHAR);// returns the first valid (long) integer value from the current position.// lookahead determines how parseInt looks ahead in the stream.// See LookaheadMode enumeration at the top of the file.// Lookahead is terminated by the first character that is not a valid part of an integer.// Once parsing commences, 'ignore' will be skipped in the stream.float parseFloat(LookaheadMode lookahead = SKIP_ALL, char ignore = NO_IGNORE_CHAR);// float version of parseIntsize_t readBytes( char *buffer, size_t length); // read chars from stream into buffersize_t readBytes( uint8_t *buffer, size_t length) { return readBytes((char *)buffer, length); }// terminates if length characters have been read or timeout (see setTimeout)// returns the number of characters placed in the buffer (0 means no valid data found)size_t readBytesUntil( char terminator, char *buffer, size_t length); // as readBytes with terminator charactersize_t readBytesUntil( char terminator, uint8_t *buffer, size_t length) { return readBytesUntil(terminator, (char *)buffer, length); }// terminates if length characters have been read, timeout, or if the terminator character  detected// returns the number of characters placed in the buffer (0 means no valid data found)// Arduino String functions to be added hereString readString();String readStringUntil(char terminator);protected:long parseInt(char ignore) { return parseInt(SKIP_ALL, ignore); }float parseFloat(char ignore) { return parseFloat(SKIP_ALL, ignore); }// These overload exists for compatibility with any class that has derived// Stream and used parseFloat/Int with a custom ignore character. To keep// the public API simple, these overload remains protected.struct MultiTarget {const char *str;  // string you're searching forsize_t len;       // length of string you're searching forsize_t index;     // index used by the search routine.};// This allows you to search for an arbitrary number of strings.// Returns index of the target that is found first or -1 if timeout occurs.int findMulti(struct MultiTarget *targets, int tCount);
};
class Print
{private:int write_error;size_t printNumber(unsigned long, uint8_t);size_t printFloat(double, uint8_t);protected:void setWriteError(int err = 1) { write_error = err; }public:Print() : write_error(0) {}int getWriteError() { return write_error; }void clearWriteError() { setWriteError(0); }virtual size_t write(uint8_t) = 0;size_t write(const char *str) {if (str == NULL) return 0;return write((const uint8_t *)str, strlen(str));}virtual size_t write(const uint8_t *buffer, size_t size);size_t write(const char *buffer, size_t size) {return write((const uint8_t *)buffer, size);}// default to zero, meaning "a single write may block"// should be overriden by subclasses with bufferingvirtual int availableForWrite() { return 0; }size_t print(const __FlashStringHelper *);size_t print(const String &);size_t print(const char[]);size_t print(char);size_t print(unsigned char, int = DEC);size_t print(int, int = DEC);size_t print(unsigned int, int = DEC);size_t print(long, int = DEC);size_t print(unsigned long, int = DEC);size_t print(double, int = 2);size_t print(const Printable&);size_t println(const __FlashStringHelper *);size_t println(const String &s);size_t println(const char[]);size_t println(char);size_t println(unsigned char, int = DEC);size_t println(int, int = DEC);size_t println(unsigned int, int = DEC);size_t println(long, int = DEC);size_t println(unsigned long, int = DEC);size_t println(double, int = 2);size_t println(const Printable&);size_t println(void);virtual void flush() { /* Empty implementation for backward compatibility */ }
};

我们发现Stream类继承自Print类,而我们熟悉的println、print、write等输出函数就在Print中,available()、read()、peak()等输入函数在Stream中。而write、available()、read()、peek()等是纯虚函数,Print类中的和输出有关的函数最后调用的都是write函数,Stream类中和读取有关的函数最后都是调用的read()和peak()。
子类通过实现以上的纯虚函数来实现输入输出函数的重定向。子类所示例化的对象都可以使用这些函数来进行输入和输出。
在使用c语言开发时,我们曾通过重写fputc()函数,来重定向的printf函数。重定向到串口1时就只能输出到串口1,而不能输出到其他的输出设备中。而使用c++类的纯虚函数重定向后,每个实例化的输出设备都有自己的输出函数。并且相比于printf,arduino的println和print通过函数重载提供了更为方便的接口。
由此我们可以对Arduino输入输出流的继承关系用下图来表示:

二、设计自己的输入输出类

我在之前的文章中说过,我并不是为了全盘移植Arduino的东西。对于Arduino输入输出类暂时无法摸清底层硬件的具体实现,为了自己的底层库兼容,我以Arduino的Stream类和Print类作为参考,自己实现了outputStream和inputStream类,其中既移植了部分Arduino输入输出类中的函数,也增加了自己的思想和风格。
首先是输入输出流的继承关系,我将输入和输出分开,作为两个独立的基类,而不像Arduino把输入类继承自输出类,以此我自己的输入输出类的继承关系如图所示:

下面我将对这两个类进行说明。

2.1 outputStream
outputStream中有两个纯虚函数成员:

virtual size_t write(uint8_t) = 0;
virtual size_t write(const uint8_t *, size_t) = 0;

这两个纯虚函数分别用于输出一个字节和输出二进制数据。类中的其他的输出函数如print、println等最终调用的都是这两个函数,因此子类只需要将这两个纯虚函数实现,就可以实例化一个输出设备。

在output类中还有两个比较重要的私有成员函数:

 size_t printNumber(unsigned long, uint8_t);size_t printFloat(double, uint8_t);

这printNumber()可以将整数以任意进制输出,最高可以使用到16进制。print和println函数通过参数缺省的特性,将默认值设为10进制。printFloat()可以将浮点数以保留任意位小数输出,print和println函数通过参数缺省的特性,设置默认值为保留小数点后2位。print和println输出整数和浮点数都会调用这两个函数。

再来看看outputStream完整声明:

class outputStream
{private:size_t printNumber(unsigned long, uint8_t);size_t printFloat(double, uint8_t);public:virtual size_t write(uint8_t) = 0;size_t write(const char *str){if (str == NULL)return 0;return write((const uint8_t *)str, strlen(str));}virtual size_t write(const uint8_t *, size_t) = 0;size_t write(const char *buffer, size_t size){return write((const uint8_t *)buffer, size);}// size_t print(const __FlashStringHelper *);size_t print(const String &);size_t print(const char[]);size_t print(char);size_t print(unsigned char, int = DEC);size_t print(int, int = DEC);size_t print(unsigned int, int = DEC);size_t print(long long, int = DEC);size_t print(unsigned long long, int = DEC);size_t print(double, int = 2);// size_t print(const Printable &);// size_t println(const __FlashStringHelper *);size_t println(const String &s);size_t println(const char[]);size_t println(char);size_t println(unsigned char, int = DEC);size_t println(int, int = DEC);size_t println(unsigned int, int = DEC);size_t println(long long, int = DEC);size_t println(unsigned long long, int = DEC);size_t println(double, int = 2);// size_t println(const Printable &);size_t println(void) { return print("\r\n"); };template <typename T>outputStream &operator<<(T out){print(out);return *this;}outputStream &operator<<(String &str){print(str);return *this;}
};

首先对比Arduino的输出类Print,Arduino中的write、print、println以及printNumber、printFloat等基本上移植了过来。Print中的flush、availableForWrite以及和error相关的函数在使用时基本上不会用到,所有没有移植。
在我自己的outputStream中添加了如下的成员函数:

 template <typename T>outputStream &operator<<(T out){print(out);return *this;}outputStream &operator<<(String &str){print(str);return *this;}

通过重载输出运算符<<,使得outputStream类支持了类似c++中std::cout的功能。这是我对Print的一个扩展。

接下来,通过串口输出来展示其功能(硬件串口类HardwareUART继承自outputStream和inputStream,以后会进行介绍)

#include "User.h"HardwareUART UART(UART_1);
int main()
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);systick_init();adc_init();/********************************************************************/UART.begin(115200);int a = 100, pi = PI;String str = "hello wold string";UART.print("a=");UART.print(a);UART.print(" 0x");UART.println(a, HEX);UART.print("pi=");UART.print(pi);UART.print(" ");UART.println(pi, 6);UART.print("hello wrold\r\n");UART.println("hello wrold");UART.println(str);UART << "print by \"<<\":" << endl;UART << "a=" << a << endl;UART << "pi=" << pi << endl;UART << "Hello wrold" << endl;UART << str << endl;UART.println(123);UART.println(123.45);UART << 123 << " " << 123.45 << endl;/********************************************************************/while (1){}
}


通过以上示例,大家应该不难发现,使用<<输出运算符会比print和println易用性更强一些。

2.2 inputStream
对于inputStream的设计会比Ardunio的Stream简洁许多,保留了Stream中的available()、read()、peek()。重载了>>输入运算符,使得inputStream子类实例化的输入设备支持c++中类似于std::cin的功能。inputSream大部分的输入函数都是设计为位阻滞函数,没有像Arduino那样可以设置等待时间,在这一点上我嘚inputStream还不够完善,后面还会继续进行修改和扩展。同时inputStream中带有一个缓冲区指针,指向输入设备的接收缓冲区(例如之前的文章中提到的串口接收缓冲区)。

首先介绍成员结构体指针:__rec_buf *buf;
__rec_buf 在之前的文章:从新建工程开始使用C++开发单片机(以STM32为例):五、C语言接口层之串口UART 进行了介绍,这里再具体展现一下:

typedef struct
{volatile uint8_t buf[256];      //接收缓冲区volatile uint8_t write_index;   //写指针volatile uint8_t read_index;    //读指针volatile uint16_t data_size;    //缓冲区接收到的数据长度/* data */
}__rec_buf;

inputStream中的read、peek、available就是在读取和维护这个缓存区。在串口的类中,将__rec_buf指针指向所对应的串口缓冲区,就可以实现对串口输入的读取。对于例如键盘的类,也可以通过建立上面的缓冲区,将指针指向其缓冲区,实现对键盘输入的读取。

相比于Arduino的Stream强调查找,例如和find相关的函数,inputStream更加强调多样化的扫描输入,因此我并没有移植Stream中find相关的函数。对于find我更偏向于把它放在String字符串类中。inputStream类有许多的scan函数,和outputStream中的print相对应,用于实现类似于C语言中scanf的功能。并且设计了scanNumber()和scanFloat()对应outputSream中的printNumber()和printFloat(),大部分的scan数字的函数底层都是scanNumber()和scanFloat()。

再来看看outputStream完整声明:

class inputStream
{protected:__rec_buf *buf;public:virtual int available();virtual int read();virtual int peek();/***阻滞读取**********************/long scanNumber();double scanFloat();int getc();int scan(char *); //扫描字符串int scan(String &);int scan(char &);int scan(unsigned char &);      //扫描字符int scan(int &);                //扫描数字int scan(unsigned int &);       //扫描数字int scan(long long &);          //扫描数字int scan(unsigned long long &); //扫描数字int scan(double &);             //扫描浮点数int scan(float &);              //扫描浮点数template <typename T>inputStream &operator>>(T &out){scan(out);return *this;}
};

相比于Arduino的Stream是不是简单了很多?
在scan的基础上,重载了>>输入运算符,和outputStream中的<<输出运算符相对应,增加了易用性。

 template <typename T>inputStream &operator>>(T &out){scan(out);return *this;}

接下来这段代码用来展示其功能:

#include "User.h"HardwareUART UART(UART_1);
int main()
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);systick_init();adc_init();/********************************************************************/UART.begin(115200);int a;float f;String str;char buf[100];UART << "scan example:" << endl;UART.scan(a);UART.scan(f);UART.scan(str);UART.scan(buf);UART << "a=" << a << " f=" << f << " String is:" << str << " buf is" << buf << endl;UART << ">> example:" << endl;UART >> a >> f >> str >> buf;UART << "a=" << a << " f=" << f << " String is:" << str << " buf is" << buf;/********************************************************************/while (1){}
}

串口输入为:
11 11.35 str buf
22,3.14 hello,world hello
串口输出:

三、总结

这两个类是我花费大量时间所设计出来的。在目前我已完成的内容中,硬件串口、屏幕输出、AT24C04等外设和外部硬件设备的驱动类都继承了这两个类,实践证明所花的时间也是值得的。相比于C语言的sprintf、printf以及直接调用C语言层的接口,使用类来封装会占用不少的单片机资源从而影响到执行的效率,不过对于现在的内存高达几百k甚至几M的单片机来说,这些效率和内存的牺牲换来的更高效率的开发是远远值得的。在上面的例子中,有使用到了String类,这是我从Arduino移植过来的WString,非常的好用,后面的文章将会介绍到。

附:完整代码

/*file : inputStream.h*/
#ifndef __OUTPUT_STREAM_H
#define __OUTPUT_STREAM_H#include <stdio.h>
#include "WString.h"
#include "headfile.h"#define DEC 10
#define HEX 16
#define OCT 8
#ifdef BIN // Prevent warnings if BIN is previously defined in "iotnx4.h" or similar
#undef BIN
#endif
#define BIN 2//using std::endl;
#define endl "\r\n"class outputStream
{private:size_t printNumber(unsigned long, uint8_t);size_t printFloat(double, uint8_t);public:virtual size_t write(uint8_t) = 0;size_t write(const char *str){if (str == NULL)return 0;return write((const uint8_t *)str, strlen(str));}virtual size_t write(const uint8_t *, size_t) = 0;size_t write(const char *buffer, size_t size){return write((const uint8_t *)buffer, size);}// size_t print(const __FlashStringHelper *);size_t print(const String &);size_t print(const char[]);size_t print(char);size_t print(unsigned char, int = DEC);size_t print(int, int = DEC);size_t print(unsigned int, int = DEC);size_t print(long long, int = DEC);size_t print(unsigned long long, int = DEC);size_t print(double, int = 2);// size_t print(const Printable &);// size_t println(const __FlashStringHelper *);size_t println(const String &s);size_t println(const char[]);size_t println(char);size_t println(unsigned char, int = DEC);size_t println(int, int = DEC);size_t println(unsigned int, int = DEC);size_t println(long long, int = DEC);size_t println(unsigned long long, int = DEC);size_t println(double, int = 2);// size_t println(const Printable &);size_t println(void) { return print("\r\n"); };template <typename T>outputStream &operator<<(T out){print(out);return *this;}outputStream &operator<<(String &str){print(str);return *this;}
};#endif
/*file : outputStream.cpp*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdarg.h>#include "outputStream.h"// Public Methods ///* default implementation: may be overridden */size_t outputStream::print(const String &s)
{return write(s.c_str(), s.length());
}size_t outputStream::print(const char str[])
{return write(str);
}size_t outputStream::print(char c)
{return write(c);
}size_t outputStream::print(unsigned char b, int base)
{return print((unsigned long long)b, base);
}size_t outputStream::print(int n, int base)
{return print((long long)n, base);
}size_t outputStream::print(unsigned int n, int base)
{return print((unsigned long long)n, base);
}size_t outputStream::print(long long n, int base)
{if (base == 0){return write(n);}else if (base == 10){if (n < 0){int t = print('-');n = -n;return printNumber(n, 10) + t;}return printNumber(n, 10);}else{return printNumber(n, base);}
}size_t outputStream::print(unsigned long long n, int base)
{if (base == 0)return write(n);elsereturn printNumber(n, base);
}size_t outputStream::print(double n, int digits)
{return printFloat(n, digits);
}size_t outputStream::println(const String &s)
{size_t n = print(s);n += println();return n;
}size_t outputStream::println(const char c[])
{size_t n = print(c);n += println();return n;
}size_t outputStream::println(char c)
{size_t n = print(c);n += println();return n;
}size_t outputStream::println(unsigned char b, int base)
{size_t n = print(b, base);n += println();return n;
}size_t outputStream::println(int num, int base)
{size_t n = print(num, base);n += println();return n;
}size_t outputStream::println(unsigned int num, int base)
{size_t n = print(num, base);n += println();return n;
}size_t outputStream::println(long long num, int base)
{size_t n = print(num, base);n += println();return n;
}size_t outputStream::println(unsigned long long num, int base)
{size_t n = print(num, base);n += println();return n;
}size_t outputStream::println(double num, int digits)
{size_t n = print(num, digits);n += println();return n;
}// Private Methods /size_t outputStream::printNumber(unsigned long n, uint8_t base)
{char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.char *str = &buf[sizeof(buf) - 1];*str = '\0';// prevent crash if called with base == 1if (base < 2)base = 10;do{char c = n % base;n /= base;*--str = c < 10 ? c + '0' : c + 'A' - 10;} while (n);return write(str);
}size_t outputStream::printFloat(double number, uint8_t digits)
{size_t n = 0;if (isnan(number))return print("nan");if (isinf(number))return print("inf");if (number > 4294967040.0)return print("ovf"); // constant determined empiricallyif (number < -4294967040.0)return print("ovf"); // constant determined empirically// Handle negative numbersif (number < 0.0){n += print('-');number = -number;}// Round correctly so that print(1.999, 2) prints as "2.00"double rounding = 0.5;for (uint8_t i = 0; i < digits; ++i)rounding /= 10.0;number += rounding;// Extract the integer part of the number and print itunsigned long long int_part = (unsigned long long)number;double remainder = number - (double)int_part;n += print(int_part);// Print the decimal point, but only if there are digits beyondif (digits > 0){n += print('.');}// Extract digits from the remainder one at a timewhile (digits-- > 0){remainder *= 10.0;unsigned int toPrint = (unsigned int)(remainder);n += print(toPrint);remainder -= toPrint;}return n;
}
/*file : inputStream.h*/
#ifndef __INPUT_STREAM_H
#define __INPUT_STREAM_H#include "WString.h"
#include "headfile.h"class inputStream
{protected:__rec_buf *buf;public:virtual int available();virtual int read();virtual int peek();/***阻滞读取**********************/long scanNumber();double scanFloat();int getc();int scan(char *); //扫描字符串int scan(String &);int scan(char &);int scan(unsigned char &);      //扫描字符int scan(int &);                //扫描数字int scan(unsigned int &);       //扫描数字int scan(long long &);          //扫描数字int scan(unsigned long long &); //扫描数字int scan(double &);             //扫描浮点数int scan(float &);              //扫描浮点数template <typename T>inputStream &operator>>(T &out){scan(out);return *this;}
};#endif
/*file : inputStream.cpp*/
#include "inputStream.h"
#include "math.h"
int inputStream::available()
{return buf->data_size;
}int inputStream::read()
{if (buf->data_size == 0){return -1;}buf->data_size--;return buf->buf[buf->read_index++];
}int inputStream::peek()
{if (buf->data_size == 0){return -1;}return buf->buf[buf->read_index];
}int inputStream::getc()
{while (!available());return read();
}long inputStream::scanNumber()
{char ch;do{ch = getc();} while (ch != '-' && (ch <= '0' || ch >= '9'));long res;int fu_flag = 0;if (ch == '-'){res = 0;fu_flag = 1;}else{res = ch - '0';}ch = getc();while (ch >= '0' && ch <= '9'){res *= 10;res += (ch - '0');ch = getc();}if (fu_flag)res = -res;return res;
}double inputStream::scanFloat()
{char ch;do{ch = getc();} while (ch != '-' && (ch <= '0' || ch >= '9'));double res;int fu_flag = 0;if (ch == '-'){res = 0;fu_flag = 1;}else{res = (double)(ch - '0');}ch = getc();while (ch >= '0' && ch <= '9'){res *= 10.0;res += (double)(ch - '0');ch = getc();}if (ch != '.'){if (fu_flag)res = -res;return res;}double d = 0.1;while (1){ch = getc();if (ch < '0' || ch > '9')break;res += d * (double)(ch - '0');d /= 10.0;}if (fu_flag)res = -res;return res;
}int inputStream::scan(char &in)
{in = getc();return in;
}int inputStream::scan(unsigned char &in)
{in = scanNumber();return in;
}int inputStream::scan(int &in)
{in = scanNumber();return in;
}int inputStream::scan(unsigned int &in)
{in = scanNumber();return in;
}int inputStream::scan(long long &in)
{in = scanNumber();return in;
}int inputStream::scan(unsigned long long &in)
{in = scanNumber();return in;
}int inputStream::scan(double &in)
{in = scanFloat();return (int)in;
}int inputStream::scan(float &in)
{in = scanFloat();return (int)in;
}int inputStream::scan(char *in)
{char ch;do{ch = getc();} while (ch <= ' ' || ch > '~');int pos = 0;in[pos++] = ch;while (1){ch = getc();if (ch <= ' ' || ch > '~'){break;}in[pos++] = ch;}in[pos] = '\0';return pos;
}int inputStream::scan(String &in)
{in = "";char ch;do{ch = getc();} while (ch <= ' ' || ch > '~');in += ch;while (1){ch = getc();if (ch <= ' ' || ch > '~'){break;}in += ch;}return in.length();
}

从新建工程开始使用C++开发单片机(以STM32为例):六、C++输入输出流(附代码)相关推荐

  1. 从新建工程开始使用C++开发单片机(以STM32为例):一、项目介绍

    一.项目初衷 大学期间使用单片机开发的项目做过不少,从平时的课程设计,到智能车比赛.电赛等,使用过的单片机包括51.STM32.英飞凌的TC264.沁恒的CH32.TI的430等等,平时自己做些小玩意 ...

  2. 欧姆龙旋转编码器接入单片机(STM32为例)的方法

    欧姆龙编码器接入单片机实现脉冲计数 前言 一.OMRON E6B2-CWZ6C 二.接入过程 1.编码器线序定义 2.3D建模 1.编码器支架 2.欧姆龙旋转编码器 3.SW装配体模型 总结(要想快, ...

  3. VS code下开发单片机或者STM32程序

    一直想着编写单片机和32代码能不能换IDE,奈何关注了很久都没有发现一个比较好的.今天突然发现VS code竟然出了一个Keil Assistant的插件,虽说不能完全取代Keil,但是也足够方便与强 ...

  4. GIS开发/WebGIS开发零基础入门:地图域当前信息(附代码)

    示例功能     该示例底图显示一个天地图世界地图,实现显示地图域当前信息功能. 示例实现     本示例需要使用 [include-openlayers-local.js] ,通过 ol.view( ...

  5. 三维GIS开发:利用Cesium加载 M3D 地质体模型(附代码)

    实现步骤 Step 1. 引用开发库: 本示例引用 local 本地[include-cesium-local.js]开发库,完成此步骤后才可调用三维 WebGL 的功能: Step 2. 创建布局: ...

  6. android开发:保存并且记住账号密码,输入输出流

    保存到data里面 方法一:这种保存方法并不提倡使用,因为绝对路径换一个环境可能就不能使用了 //传过来的参数用##拼接成一个字符串String info=name+"##"+pa ...

  7. 如何快速的开发单片机

    作为一个单片机开发的老司机,在这里告诉大家如何快速的开发单片机,完成自己的项目. 一.整理需求,如果你们的产品经理已经整理出需求那就更好了.拿到需求后,如果有疑问请及时沟通.请不要有疑点,例如:完成项 ...

  8. C语言开发单片机为什么大多数都采用全局变量的形式?

    一个代码狗,成长经历都是莫名相似的. 你应该和我有类似的经历,虽然功能都能做出来,但是总觉得自己代码缺了点什么. 就是怎么写都觉得不够完美,感觉代码的水平介于专业和不专业之间. 自己的认知水平也非常有 ...

  9. 开发单片机常见的IDE有哪些?

    开发单片机常见的IDE有哪些? (点击链接阅读原文)

最新文章

  1. 非技术成本继续困扰光伏产业
  2. java中List Array相互转换
  3. 自动驾驶前沿综述:基于深度强化学习的自动驾驶算法
  4. Android开发 ShapeDrawable详解
  5. VS2008如何添加 OLE/COM 对象查看器 .
  6. linux虚拟机桥接网络配置
  7. 中国互联网大佬江湖拼什么?拼财力拼出身拼前景拼造势
  8. activiti 5.22的demo运行
  9. diff算法阮一峰_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法
  10. laravel-神奇的服务容器(转)
  11. 如何解决ArrayAdapter requires the resource ID to be a TextView
  12. Navicat连接本地数据库报错问题解决方案
  13. matlab实验是啥,实验一 MATLAB的实验环境及基本命令.ppt
  14. 【算法】排序_基数排序
  15. python备注(持续更新……)
  16. c# 循环com,分别对串口写入与读取,获取需要的串口信息
  17. 家用宽带搭建个人服务器(一)
  18. 关于高德地图标注的那些坑
  19. 树上随机游走的期望距离
  20. 小程序开发可以用什么语言?

热门文章

  1. 软件测试和硬件测试的区别及概念
  2. 5s注销了id新建id服务器出错,iphone5s手机如何重新设置苹果id账户
  3. 无水印思维导图——导出亿图(试用版)无水印图片
  4. ajax向后台传中文乱码问题
  5. 游戏思考系列03:游戏匹配机制(MMR、ELO、trueskill2、皇家战争、Glicko等,详细讲ELO,其他的简略)
  6. 阿里云盘内测申请_阿里云网盘公测预约开始了,现在申请还送2个T的空间!
  7. 小米 11 Ultra 正式发布,自称 “安卓之光”
  8. Corn fields(玉米田)状压dp入门第一题 洛谷P1879 poj3254
  9. Jackson配置大全
  10. win7桌面图标全变成windows media center 解决办法