printf重定向方法

嵌入式开发中串口是调试的常用方式,printf​作为调试最常用的方式是需要重定向的,不同的编译器有不同的重定向方式。


1、GCC开发环境

#if !defined(__CROSSWORKS_ARM) && defined(__GNUC__)

#include <sys/stat.h>
#include <stddef.h>


int _close(int file);
void _exit(int status);
int _fstat(int file, struct stat *st);
int _getpid(void);
int _isatty(int file);
int _kill(int pid, int sig);
int _lseek(int file, int ptr, int dir);
int _read(int file, char *ptr, int len);
int _write(int file, const char *ptr, int len);

/**************************************************************************//**
* Close a file.
*
* @param[in] file File you want to close.
*
* @return Returns 0 when the file is closed.
*****************************************************************************/
int _close(int file)
{
  (void) file;
  return 0;
}

/**************************************************************************//**
* Exit the program.
*
* @param[in] status The value to return to the parent process as the
* exit status (not used).
*****************************************************************************/
void _exit(int status)
{
  (void) status;
  while (1) {
  } // Hang here forever...
}

/**************************************************************************//**
* Status of an open file.
*
* @param[in] file Check status for this file.
*
* @param[in] st Status information.
*
* @return Returns 0 when st_mode is set to character special.
*****************************************************************************/
int _fstat(int file, struct stat *st)
{
  (void) file;
  st->st_mode = S_IFCHR;
  return 0;
}

/**************************************************************************//**
* Get process ID.
*
* @return Return 1 when not implemented.
*****************************************************************************/
int _getpid(void)
{
  return 1;
}

/**************************************************************************//**
* Query whether output stream is a terminal.
*
* @param[in] file Descriptor for the file.
*
* @return Returns 1 when query is done.
*****************************************************************************/
int _isatty(int file)
{
  (void) file;
  return 1;
}

/**************************************************************************//**
* Send signal to process.
*
* @param[in] pid Process id (not used).
*
* @param[in] sig Signal to send (not used).
*****************************************************************************/
int _kill(int pid, int sig)
{
  (void)pid;
  (void)sig;
  return -1;
}

/**************************************************************************//**
* Set position in a file.
*
* @param[in] file Descriptor for the file.
*
* @param[in] ptr Poiter to the argument offset.
*
* @param[in] dir Directory whence.
*
* @return Returns 0 when position is set.
*****************************************************************************/
int _lseek(int file, int ptr, int dir)
{
  (void) file;
  (void) ptr;
  (void) dir;
  return 0;
}

/**************************************************************************//**
* Read from a file.
*
* @param[in] file Descriptor for the file you want to read from.
*
* @param[in] ptr Pointer to the chacaters that are beeing read.
*
* @param[in] len Number of characters to be read.
*
* @return Number of characters that have been read.
*****************************************************************************/
int _read(int file, char *ptr, int len)
{
  (void)file;

  return readBuffer(ptr, len);
}

/**************************************************************************//**
* Write to a file.
*
* @param[in] file Descriptor for the file you want to write to.
*
* @param[in] ptr Pointer to the text you want to write
*
* @param[in] len Number of characters to be written.
*
* @return Number of characters that have been written.
*****************************************************************************/
int _write(int file, const char *ptr, int len)
{
  (void)file;

  return writeBuffer(ptr, len);
}

#endif /* !defined( __CROSSWORKS_ARM ) && defined( __GNUC__ ) */

在自己的程序中,只需实现:

int writeBuffer(char *ch, int length);
int readBuffer(char *ch, int length);

2、keil开发环境

2.1、使用MicroLib

在代码中实现fputc​函数

// 重定向printf
int fputc(int ch, FILE *f){
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000); 
  return ch;
}

// 重定向getchar
int fgetc(FILE *f)
{
  int ch;
  while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) == RESET);
  HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
  return (ch);
}

2.2、使用非半主机模式

新建syscalls.c

#if defined(__CC_ARM)
/******************************************************************************/
/* RETARGET.C: 'Retarget' layer for target-dependent low-level functions */
/******************************************************************************/
/* This file is part of the uVision/ARM development tools. */
/* Copyright (c) 2005-2006 Keil Software. All rights reserved. */
/* This software may only be used under the terms of a valid, current, */
/* end user licence from KEIL for a compatible version of KEIL software */
/* development tools. Nothing else gives you the right to use this software. */
/******************************************************************************/

#include <stdio.h>

#pragma import(__use_no_semihosting_swi)

struct __FILE{
  int handle;
};

//Standard output stream
FILE __stdout;

/**************************************************************************//**
* Writes character to file
*
* @param[in] f File
*
* @param[in] ch Character
*
* @return Written character
*****************************************************************************/
int fputc(int ch, FILE *f)
{
  return putChar(ch);
}

/**************************************************************************//**
* Reads character from file
*
* @param[in] f File
*
* @return Character
*****************************************************************************/
int fgetc(FILE *f)
{
return getChar();
}

/**************************************************************************//**
* Tests the error indicator for the stream pointed to by file
*
* @param[in] f File
*
* @return Returns non-zero if it is set
*****************************************************************************/
int ferror(FILE *f)
{
  // Your implementation of ferror
  return EOF;
}

/**************************************************************************//**
* Writes a character to the console
*
* @param[in] ch Input character
*****************************************************************************/
void _ttywrch(int ch)
{
  putChar(ch);
}

/**************************************************************************//**
* Library exit function. This function is called if stack overflow occurs.
*
* @param[in] return_code Return code
*****************************************************************************/
void _sys_exit(int return_code)
{
  label: goto label; // endless loop
}

#endif /* defined( __CC_ARM ) */

BP神经网络实现手势识别

第一次自己做手势识别就是在大三那年的大学生电子设计竞赛,那也是我第一次开始思考“算法”。那一年的电赛有一道题目,使用FDC2214(TI的一款四通道电容传感器)设计一个识别装置,要求能够识别,“石头”、“剪刀”、“布”,三种手势以及,放置了几个手指。

拿到题目的我最先想到的就是在识别区域放置四组电极,当手放在电极上方区域的时候记录四组电极的电容值,然后按照阈值判断到底属于哪个手势。

18年电赛时手工制作的电极

但是随着一点点的把想法变成现实,问题渐渐的浮现了出来,首先就是确定阈值时候的难度超过了我的想象,因为有四个通道,需要同时满足条件才能判定为某个手势,这就导致你必须合理的安排每一个手势的每一个通道的阈值,而起比赛要求能够学习不同人的手势,这让这个方案实施起来变得极其困难。在一个就是手放在电极上方测出来的电容很容易受到环境的影响,而阈值判别的方式容错性又极低。在尝试了一番后就放弃了这个方案,转而开始想其他的办法。

这个问题的本质其实是怎么判断一个数据到底是属于哪一个集合,也就是说着其实是一个典型的分类问题,那么就从常用的分类算法入手,首先想到的就是神经网络,但当时也就是想到,因为主控使用的是STM32,而且最主要的是我那时候只是听说过神经网络,根本就不会,所以神经网络也就是一闪而过的念头。在对比了好多分类算法后,最终选择了一个最容易实现的——KNN(k-近邻)算法。虽然KNN相比其它的分类如果样本集比较复杂,可能会导致很大的计算开销,因此一般不会应用到实时性很强的场合,但是在这个场景中只有四个通道,不多的几种手势,就算是在STM32上也会有很好的速度。

最终的比赛作品

虽然当时是哟KNN取得了不错的成绩,也结局了这个问题,但是那个一闪而过的想法却从那之后总会时不时的蹦出我的脑海,这也促使我尝试搞懂神经网络并用神经网络实现当初的功能。

白云苍狗,我都已经毕业三年了,当时的想法我也终于实现了出来,虽然晚了点,但好歹没有缺席。

在明白了神经网络的工作原理后我就迫不及待的用C语言在单片机上实现了网络的前向推理,但是当实现反向传播的时候又难到了我,于是我就使用Python先写了一个简单的前向和反向的代码验证。

# -*- coding: UTF-8 -*-

'''
反向传播神经网络
'''
from os import times
import numpy
from numpy import random
from numpy.core.fromnumeric import shape
import scipy.special
import time 
import matplotlib as mpl
import matplotlib.pyplot as plt


class neuralNetwork:
    
    #初始化神经网络,输入层的节点数,隐藏层的节点数,输出层的节点数,学习率
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate ):
        #初始化网络参数
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        self.lr = learningrate
        #初始化网络权重
        self.wih = numpy.random.normal( 0.0, pow(self.hnodes, -0.5),(self.hnodes, self.inodes))
        self.bh = numpy.random.normal( 0.0, pow(self.hnodes, -0.5),(self.hnodes,1))
        self.who = numpy.random.normal( 0.0, pow(self.onodes, -0.5),(self.onodes, self.hnodes))
        self.bo = numpy.random.normal( 0.0, pow(self.hnodes, -0.5),(self.onodes,1))
        #激活函数
        self.activation_function = lambda x: scipy.special.expit(x)
        pass    
    #神经网络训练
    def train(self, inputs_list, targets_list):
        #将输入的列表转换成矩阵
        inputs = numpy.array(inputs_list, ndmin=2).T
        targets = numpy.array(targets_list, ndmin=2).T
        #计算中间层的输入
        hidden_inputs = numpy.dot(self.wih, inputs)+self.bh
        #中间层应用激活函数
        hidden_outputs = self.activation_function(hidden_inputs)
        #计算输出层的输入
        final_inputs = numpy.dot(self.who, hidden_outputs)+self.bo
        #输出层应用激活函数
        final_outputs = self.activation_function(final_inputs)
        #计算误差矩阵
        output_errors = targets - final_outputs
        hidden_errors = numpy.dot(self.who.T, output_errors)
        #更新权重误差
        self.who += self.lr * numpy.dot((output_errors *final_outputs * (1.0 - final_outputs)),numpy.transpose(hidden_outputs))
        self.bo += self.lr * output_errors
        self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
        self.bh += self.lr * hidden_errors
        #print("output_error:\n",output_errors,"\nbo:\n",self.bo,"\nhidden_errors:\n",hidden_errors,"\nbn:\n",self.bh)

        Error = 0
        for num in output_errors:
            Error = Error + abs(num)
        return Error
        pass
    #推理
    def query(self, inputs_list):
        inputs = numpy.array(inputs_list, ndmin=2).T
        hidden_inputs = numpy.dot(self.wih, inputs)+self.bh
        hidden_outputs = self.activation_function(hidden_inputs)
        final_inputs = numpy.dot(self.who, hidden_outputs)+self.bo
        final_outputs = self.activation_function(final_inputs)
        return final_outputs
        pass


input_nodes = 4
hidden_nodes = 200
output_nodes = 3
learning_rate = 0.7

epoch = 10000

n = neuralNetwork(input_nodes,hidden_nodes,output_nodes,learning_rate)

in_list = [[0.000,0.030,0.750,0.500],[0.200,0.300,0.800,0.500],[0.200,0.600,0.900,0.500],[0.065,0.275,0.960,1.2],[0.460,0.500,1.100,1.200],[0.700,1.00,1.400,1.200]]
out_list = [[1,0,0],[0,1,0],[0,0,1],[1,0,0],[0,1,0],[0,0,1]]

i = 0
s_time = time.clock()
error_list = []

while i<epoch:
    print_error = 0
    for l in range(6):
        print_error = print_error + n.train(in_list[l],out_list[l])
    print("epoch:",i,"Error:",print_error/6)
    error_list.append(print_error/6)
    i=i+1
e_time = time.clock()
#输出训练用时
print(s_time,e_time,e_time-s_time)

x=numpy.linspace(0,epoch,epoch)
plt.plot(x,error_list)
plt.show()

print_list = ["石头","剪刀","布"]

while(True):
    a = float(input("CH1:"))
    b = float(input("CH2:"))
    c = float(input("CH3:"))
    d = float(input("CH4:"))
    out = n.query([a,b,c,d])
    out = out.tolist()
    print(out)
    Max_num = out.index(max(out))
    print("当前手势是:",print_list[Max_num])

果然是“人生苦短,我用Python”,这种要啥有啥的感觉真的太爽,不需要考虑内存,不需要考虑矩阵运算的细节….. 不过虽然用着是挺爽的,但是如果想部署到单片机上那还得自己手撸一个,不过既然已经在Python上成功了那只用使用C再实现一遍就好了,当然也可以在单片机上使用用MicroPython不过考虑到单片机的资源以及MicroPython支持的并不是十分完善,所以我还是选择用C写。

(未完待续 ······)