一、前言:
在FastBond项目的第一阶段,在Scheme-it软件中绘制了详细的框图和原理图。这个过程包括了对整个系统的设计思路的梳理,以及各个模块之间的连接关系的确定。同时,也进行了物料的选型,确保选用的物料能够满足项目的性能需求和成本控制。接下来在kicad软件中完成了PCB的绘制工作和打板工作。FastBond项目的第二阶段。在这个阶段中,会继续完善设计,做出实物模型,并进行效果演示。
二、原理图与PCB设计:
在FastBond项目的第一阶段,已经进行了PCB的绘制和打板,但是在后续进行焊接测试的时候出现了一些问题。
- 原设计使用的REG1117-5是一款州仪器(Texas Instruments)生产的降压线性稳压器,而我使用的是3.7V的锂电池进行供电,通过设计的电路发现降压到了2.7V,根本达不到MAX78000FTHR供电要求。
- 原设计的MAX78000FTHR的排母插座两端在PCB布线的时候大了100mil,两端的尺寸出现了100mil的偏差,导致不兼容。使用面包板测量的时候为9个孔的宽度,我误认为900mil,事实上需要800mil。
由图可以看到刚刚好差了一点点,为了解决这些问题,我对于物料进行了重新选型和重新打板,优化了布局,预留出锂电池的的位置。
- 排母插座两端宽度更改为800mil。
- 使用德州仪器(Texas Instruments)生产的TPS61070DDCR DC-CD电源芯片,重新设计电路。
为了符合MAX78000FTHR的接口,放置了1x16 1x12的排母插座,将宽度修改为800mil
改用TPS61070DDCR芯片来设计新的电路系统。这个电路的主要任务是对锂电池进行升压处理,以便能够提供足够的电力供应给MAX78000FTHR。通过这种方式,确保MAX78000FTHR能够稳定且高效地运行,而不会出现电力不足的问题。
TPS61070DDCR是德州仪器(Texas Instruments)推出的一款高效节能的升压直流-直流(DC-DC)转换器芯片。该芯片采用了降低功耗和提高效率的先进技术,适用于各种电池供电应用。具有 600mA 开关的 90% 高效同步升压转换器。
主要特性:
- 90% 高效同步升压转换器
- 器件静态电流:19 µA(典型值)
- 输入电压范围:0.9V 至 5.5V
- 可调输出电压高达 5.5 V
- 可提供省电模式版本,以改善低输出功率时的效率
- 停机期间负载断开
- 过温保护
- SOT-23-6封装
应用:
- 所有一节、两节和三节碱性、镍镉或镍氢电池供电型产品或单节锂电池供电型产品 】
- 便携式音频播放器
- PDA
- 蜂窝电话
- 个人医疗产品
- 白光 LED 照明
三、程序设计:
MAX78000FTHR程序开发主要有两个部分,神经网络部署和外设开发。
神经网络部署流程:
神经网络部署流程中需要注意的是,官方所提供的ai8x-training和ai8x-synthesis中完整展示了训练 量化 评估模型 生成C代码的过程,但是并没有说明如何搭建神经网络模型,需要根据MAX78000的模型网络限制进行构建神经网络模型,也就是说并不是所以的模型都可以在MAX78000上进行部署的。建议参考官方模型进行修改搭建模型,以达到更好的效果。
因为我的笔记本电脑是集显的,所以我整个过程是在服务器上完成的。配置为PyTorch 1.8.1/Python 3.8(ubuntu18.04)/Cuda 11.1。由于我的能力有限并且是第一次接触此类,所以大部分都是参考官方。
神经网络部署:
1:收集数据制作训练集和测试文件
首先先http://yann.lecun.com/exdb/mnist/index.html上下载训练集和数据集以及其对应标签,建立MNIST文件夹,将其解压放入MNIST文件夹中。并且对训练集和测试集的加载和预处理操作。
import torchvision
from torchvision import transforms
import ai8x
def my_mnist_data(data, load_train=True, load_test=True):
(data_dir, args) = data
if load_train:
train_transform = transforms.Compose([
transforms.RandomCrop(28, padding=4),
transforms.RandomAffine(degrees=20, translate=(0.1, 0.1), shear=5),
transforms.ToTensor(),
ai8x.normalize(args=args)
])
train_dataset = torchvision.datasets.MNIST(root=data_dir, train=True, download=True,
transform=train_transform)
else:
train_dataset = None
if load_test:
test_transform = transforms.Compose([
transforms.ToTensor(),
ai8x.normalize(args=args)
])
test_dataset = torchvision.datasets.MNIST(root=data_dir, train=False, download=True,
transform=test_transform)
if args.truncate_testset:
test_dataset.data = test_dataset.data[:1]
else:
test_dataset = None
return train_dataset, test_dataset
datasets = [
{
'name': 'MNIST',
'input': (1, 28, 28),
'output': list(map(str, range(10))),
'loader': my_mnist_data,
},
]
2:模型搭建
模型搭建主要是参考官方的ai85net5模型。
from torch import nn
import ai8x
class AI85Net5(nn.Module):
"""
5-Layer CNN that uses max parameters in AI84
"""
def __init__(self, num_classes=10, num_channels=3, dimensions=(28, 28),
planes=60, pool=2, fc_inputs=12, bias=False, **kwargs):
super().__init__()
# Limits
assert planes + num_channels <= ai8x.dev.WEIGHT_INPUTS
assert planes + fc_inputs <= ai8x.dev.WEIGHT_DEPTH-1
# Keep track of image dimensions so one constructor works for all image sizes
dim = dimensions[0]
self.conv1 = ai8x.FusedConv2dReLU(num_channels, planes, 3,
padding=1, bias=bias, **kwargs)
# padding 1 -> no change in dimensions -> MNIST: 28x28 | CIFAR: 32x32
pad = 2 if dim == 28 else 1
self.conv2 = ai8x.FusedMaxPoolConv2dReLU(planes, planes, 3, pool_size=2, pool_stride=2,
padding=pad, bias=bias, **kwargs)
dim //= 2 # pooling, padding 0 -> MNIST: 14x14 | CIFAR: 16x16
if pad == 2:
dim += 2 # MNIST: padding 2 -> 16x16 | CIFAR: padding 1 -> 16x16
self.conv3 = ai8x.FusedMaxPoolConv2dReLU(planes, 128-planes-fc_inputs, 3,
pool_size=2, pool_stride=2, padding=1,
bias=bias, **kwargs)
dim //= 2 # pooling, padding 0 -> 8x8
# padding 1 -> no change in dimensions
self.conv4 = ai8x.FusedAvgPoolConv2dReLU(128-planes-fc_inputs,
fc_inputs, 3,
pool_size=pool, pool_stride=2, padding=1,
bias=bias, **kwargs)
dim //= pool # pooling, padding 0 -> 4x4
# padding 1 -> no change in dimensions
self.fc = ai8x.Linear(fc_inputs*dim*dim, num_classes, bias=True, wide=True, **kwargs)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
def forward(self, x): # pylint: disable=arguments-differ
"""Forward prop"""
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
def ai85net5(pretrained=False, **kwargs):
"""
Constructs a AI85Net5 model.
"""
assert not pretrained
return AI85Net5(**kwargs)
models = [
{
'name': 'ai85net5',
'min_input': 1,
'dim': 2,
},
]
3:训练
训练主要是依靠官方的train.py 文件。
python train.py --lr 0.1 --optimizer SGD --epochs 200 --deterministic --compress policies/schedule.yaml --qat-policy qat_policy.yaml --model ai85net5 --dataset MNIST --param-hist --device MAX78000 --pr-curves
其中qat_policy.yaml 和 schedule.yaml都为官方提供的文件。
其中train.py的各参数含义如下:
- --lr:设置初始学习率
- --optimizer SGD:优化器选择为随机梯度下降(Stochastic Gradient Descent),用于更新模型的参数。
- --epochs:训练次数
- --deterministic:固定值的随机数生成器添加种子
- --compress:设置压缩和学习率调度
- --qat-policy:在YAML文件中定义QAT策略(默认:policies/qat_policy.yaml)。
- --model:训练模型
- --dataset:数据集加载目录
- --param-hist:收集参数统计信息
- --device:运行设备
- --pr-curves:生成精确召回曲线
训练后会在logs文件夹中生成以下文件
4:量化
注意要更改为为自己的路径。
python quantize.py my_train/qat_best.pth.tar my_train/my_mnist.pth.tar --device MAX78000 -v -c mnist.yaml
5:模型评估
模型评估也是依赖于train.py
python train.py --model ai85net5 --dataset MNIST --confusion --evaluate --exp-load-weights-from ../ai8x-synthesis/my_train/my_mnist.pth.tar -8 --device MAX78000
6:生成C代码
python ai8xize.py --verbose --test-dir MY_Project --prefix ai85-mnist --checkpoint-file my_train/my_mnist.pth.tar --config-file networks/mnist-chw-ai85.yaml --compact-data --softmax --device MAX78000
生成C代码主要依赖于ai8xize.py
其中ai8xize.py的各参数含义如下:
- --verbose:在运行过程中输出详细的日志信息
- --test-dir:指定生成工程存储路径
- --prefix:设置测试名前缀
- --checkpoint-file:包含量化权重的检查点文件,量化后生成的文件
- --config-flie:包含层配置的YAML配置文件
- --compact-data:输入数据方式
- --softmax:将softmax函数添加到生成的代码中
- --device:运行设备
之后会生成以下文件
其中主要的是cnn.h/weights.h/cnn.c/softmax.c。
外设开发:
在参考官方代码进行外设开发的时候,发现官方的代码应用层耦合度特别高,为了兼容不同的开发板,同一个功能在不同例程中有不同方式的实现,而且每一个例程风格都不太一样,变量定义/函数命名/注释等都特别难受,但是代码又能看得懂,一种很奇妙的平衡,我从Tools中复制了一份模板,按照自己的习惯添加文件和编写代码。
主要流程:
主要函数如下:
void App_Camera_Cnn_Get(void)
{
uint8_t *frame_buffer;
uint8_t *buffer;
uint32_t imgLen;
uint32_t w, h, x, y;
uint8_t r, g, b;
uint32_t color;
int cnt = 0;
camera_start_capture_image();
while (!camera_is_image_rcv())
{
}
camera_get_image(&frame_buffer, &imgLen, &w, &h);
buffer = frame_buffer;
for (y = 0; y < 112; y++)
{
for (x = 0; x < 112; x++)
{
r = *buffer++;
g = *buffer++;
b = *buffer++;
buffer++; // skip msb=0x00
// change the range from [0,255] to [-128,127] and store in buffer for CNN
input_0[cnt++] = ((b << 16) | (g << 8) | r) ^ 0x00808080;
color = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3);
MXC_TFT_WritePixel(x * 3, y * 3, 3, 3, color);
}
}
}
mian函数主要实现外设初始化过程,CNN推理过程和串口打印推理情况
int main(void)
{
MXC_Delay(200000);
int i;
int digs, tens;
MXC_ICC_Enable(MXC_ICC0); // Enable cache
// Switch to 100 MHz clock
MXC_SYS_Clock_Select(MXC_SYS_CLOCK_IPO);
SystemCoreClockUpdate();
// DO NOT DELETE THIS LINE:
MXC_Delay(SEC(2)); // Let debugger interrupt if needed
App_Tft_Init();
App_Camera_Init();
App_Tft_Print(buff, 20, 213, font_library, snprintf(buff, sizeof(buff), "My_Project"));
MXC_Delay(1000000);
MXC_TFT_ShowImage(0, 0, image_bitmap_1);
MXC_Delay(1000000);
MXC_TFT_ClearScreen();
// Enable peripheral, enable CNN interrupt, turn on CNN clock
// CNN clock: APB (50 MHz) div 1
cnn_enable(MXC_S_GCR_PCLKDIV_CNNCLKSEL_PCLK, MXC_S_GCR_PCLKDIV_CNNCLKDIV_DIV1);
cnn_boost_enable(MXC_GPIO2, MXC_GPIO_PIN_5);
cnn_init(); // Bring state machine into consistent state
cnn_load_weights(); // Load kernels
cnn_load_bias();
cnn_configure(); // Configure state machine
MXC_SYS_ClockEnable(MXC_SYS_PERIPH_CLOCK_CNN);
while (1)
{
App_Camera_Cnn_Get();
load_input(); // Load data input
cnn_start(); // Start CNN processing
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; // SLEEPDEEP=0
while (cnn_time == 0)
__WFI(); // Wait for CNN
/* if (check_output() != CNN_OK)
fail(); */
softmax_layer();
/* cnn_disable(); // Shut down CNN clock, disable peripheral */
for (i = 0; i < CNN_NUM_OUTPUTS; i++)
{
digs = (1000 * ml_softmax[i] + 0x4000) >> 15;
tens = digs % 10;
digs = digs / 10;
printf("[%7d] -> Class %d: %d.%d%%\n", ml_data[i], i, digs, tens);
}
asciiart((uint8_t *)input_0);
MXC_Delay(500000);
}
}
四、遇到的问题及解决方法:
1:PCB绘制错误和元器件选型错误
重新打板,选择新器件进行原理图绘制和PCB打板
2:识别效果差
输入模型应该为28*28的uint_32位数据,而摄像头捕获的数据如果直接为28*28的话,在TFT上根本看不清楚摄像头的拍摄情况,所以我选择了通过摄像头获取112*112然后再经过图形变换转换为28*28
五、功能展示图:
六:总结
MAX78000是一款性能优秀的芯片,之前只是接触过传统类的MCU,MAX78000的学习过程让我了解了人工智能相关的东西,并且学习了在Linux上通过敲指令的方式的开发MAX78000的流程,但是本人能力有限大部分参考官方实现,详细代码可以查看附件,而且数字识别的效果也一般,容易受到光照等因素的影响,作为初学者我建议参考官方的模型搭建,避免自己搭建的模型在MAX78000上的识别效果差,我这次学习依然有很多不足之处和疑惑之处,后面还需要时间进行开发优化,同样感谢硬禾团队的此次活动。