公司最近开发的一款产品用到了ambarella H2平台的一款Soc,众所周知ambarella的H2系列的Soc编码能力很强,最高可达4kp60,捕捉上没有用ambarella开发板推荐的几个捕捉卡,是自己用fpga做的一款捕捉卡, 所以捕捉卡驱动需要自己来写。

捕捉卡驱动其实没有什么东西,就是简单地i2c通信, H2 Soc通过i2c和捕捉卡进行通信, 可以通过check寄存器得到输入源的制式以及audio通道数相关信息,从而进行audio通道数设定并且通过/proc文件系统告诉应用层,从而进行相应制式的捕捉与编码。

至于制式切换方面,捕捉卡这边会向cpu发来一个gpio中断(200ms高电平),捕捉卡驱动捕获到该中断后,重新check一次寄存器值,配置auido相关寄存器并更新/proc下的制式文件,应用层通过不断地poll这个制式文件得知制式改变时,停止原先的捕捉,开启新的制式的捕捉及编码。这种方法有个缺点就是响应速度会慢一些,但是可以优化,比如驱动层通过异步通知的方法告知应用层制式切换了,然后应从层再去check /proc下制式文件。

这个i2c捕捉卡驱动要和ambarella H2 SDK里的捕捉相关驱动集成在一起,这里ambarela SDK里捕捉驱动先略过。

驱动编写

一、修改设备树文件

ambarella/boards/h2_xxx/bsp/h2_xxx.dts

apb@e8000000 {
i2c0: i2c@e8003000 {
single_vin: ambvin0@ {
compatible = "ambarella,ambvin";
reg = <0x3B>; /* slave address */
interrupt-parent = <&gpio>;
interrupts = < 0x2>; /* gpio26, 下降沿触发*/
};
status = "ok";
};
...

二、编写驱动

捕捉卡i2c的读写地址都是2bytes, 读写数据每次4bytes。0x0004为读写测试寄存器。

下面代码只实现了i2c驱动的注册、i2c读写函数、中断申请。插拔视频源时会触发中断,中断处理函数中对0x0004寄存器进行了读写测试。其他代码为ambarella平台视频捕捉部分的demo,都是hardcode的,可以在中断处理函数里调用dummyfpga_get_format, 并在dummyfpga_get_format里check捕捉卡寄存器,从而设定相应的timing去捕捉,这里不做过多的累述。

#include <linux/module.h>
#include <linux/ambpriv_device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/bitrev.h>
#include <linux/stat.h>
#include <linux/i2c.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/param.h>
#include <plat/spi.h>
#include <iav_utils.h>
#include <vin_api.h>
#include <plat/clk.h>
#include <plat/gpio.h> #include "dummyfpga_table.c" unsigned int g_audio_mode=;
unsigned int g_cap_cap_w=;
unsigned int g_cap_cap_h=; static int w = ;
MODULE_PARM_DESC(w, "video input width");
module_param(w, int, S_IRUGO); static int h = ;
MODULE_PARM_DESC(h, "video input height");
module_param(h, int, S_IRUGO); static int p = ;
MODULE_PARM_DESC(p, "video input format");
module_param(p, int, S_IRUGO); static int bit = ;
MODULE_PARM_DESC(bit, "video input format bits");
module_param(bit, int, S_IRUGO); struct xilinx_dev {
struct cdev cdev;
struct i2c_client *client;
struct class *cls;
struct mutex rw_mutex;
}; //struct xilinx_dev *xilinx_devp;
static int xilinx_cdev_major = ; static int dummyfpga_set_audio_mode_width_height(struct vin_device *vdev, u32 width, u32 height)
{
g_audio_mode = ;
g_cap_cap_w = width;
g_cap_cap_h = height; return ;
} static int dummyfpga_set_vin_mode(struct vin_device *vdev, struct vin_video_format *format)
{
struct vin_device_config dummyfpga_config;
static int yuv_order = SENSOR_CB_Y0_CR_Y1; memset(&dummyfpga_config, , sizeof (dummyfpga_config)); dummyfpga_config.sensor_id = GENERIC_SENSOR;
dummyfpga_config.interface_type = SENSOR_PARALLEL_LVDS;
if (bit == ) {
dummyfpga_config.bit_resolution = AMBA_VIDEO_BITS_10;
} else {
dummyfpga_config.bit_resolution = AMBA_VIDEO_BITS_8;
}
dummyfpga_config.input_mode = SENSOR_YUV_2PIX;
//dummyfpga_config.input_mode = SENSOR_YUV_1PIX;
dummyfpga_config.plvds_cfg.data_edge = SENSOR_DATA_FALLING_EDGE;
//if (w == 720) {
// dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_ACROSS_BOTH;
// } else {
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
// } dummyfpga_config.plvds_cfg.data_rate = SENSOR_PARALLEL_DATA_RATE_SDR;
dummyfpga_config.plvds_cfg.a8_mode = SENSOR_PARALLEL_NONE_A8_MODE;
dummyfpga_config.yuv_pixel_order = yuv_order;
dummyfpga_config.plvds_cfg.hw_specific = SENSOR_PARALLEL_HW_BUB;
//dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
//dummyfpga_config.input_format = AMBA_VIN_INPUT_FORMAT_YUV_422_INTLC; if (p == ) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.input_format = AMBA_VIN_INPUT_FORMAT_YUV_422_PROG;
} else {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_INTERLACE;
dummyfpga_config.input_format = AMBA_VIN_INPUT_FORMAT_YUV_422_INTLC;
} /*
if(format->video_mode == AMBA_VIDEO_MODE_1080P || format->video_mode == AMBA_VIDEO_MODE_1080P50)
{
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else if(format->video_mode == AMBA_VIDEO_MODE_1080I || format->video_mode == AMBA_VIDEO_MODE_1080I50) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else if(format->video_mode == AMBA_VIDEO_MODE_720P || format->video_mode == AMBA_VIDEO_MODE_720P50) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else if(format->video_mode == AMBA_VIDEO_MODE_576I) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else if(format->video_mode == AMBA_VIDEO_MODE_480I) {
dummyfpga_config.plvds_cfg.sync_code_style = SENSOR_SYNC_STYLE_ITU656;
dummyfpga_config.yuv_pixel_order = SENSOR_Y0_CB_Y1_CR;
dummyfpga_config.plvds_cfg.emb_sync_loc = SENSOR_PARALLEL_SYNC_LOWER_PIX;
} else {
printk("Unsupport mode %d \n", format->video_mode);
return -1;
}
*/
dummyfpga_config.cap_win.x = ;
dummyfpga_config.cap_win.y = ;
dummyfpga_config.cap_win.width = w;
dummyfpga_config.cap_win.height = h; printk("cap_win_height %d video_format %d \n", dummyfpga_config.cap_win.height, format->format);
dummyfpga_config.video_format = format->format ;
return ambarella_set_vin_config(vdev, &dummyfpga_config);
} static int dummyfpga_set_format(struct vin_device *vdev, struct vin_video_format *format)
{
int rval; rval = dummyfpga_set_vin_mode(vdev, format);
if (rval < )
return rval; printk("############### Debug: dummyfpga_set_format ##############\n");
return ;
} static int dummyfpga_get_format(struct vin_device *vdev)
{
//int rval; vdev->formats->video_mode = AMBA_VIDEO_MODE_AUTO;
vdev->formats->def_start_x = ;//pinfo->cap_start_x;
vdev->formats->def_start_y = ;//pinfo->cap_start_y;
vdev->formats->def_width = g_audio_mode ? g_cap_cap_w:w;//pinfo->cap_cap_w;
vdev->formats->def_height = g_audio_mode ? g_cap_cap_h:h;//pinfo->cap_cap_h;
vdev->formats->default_fps = AMBA_VIDEO_FPS_60;//pinfo->frame_rate;
vdev->formats->max_fps = AMBA_VIDEO_FPS_60;//pinfo->frame_rate;
vdev->formats->ratio = AMBA_VIDEO_RATIO_16_9;//pinfo->aspect_ratio;
if (p == ) {
vdev->formats->format = AMBA_VIDEO_FORMAT_PROGRESSIVE;//pinfo->video_format;
} else {
vdev->formats->format = AMBA_VIDEO_FORMAT_INTERLACE;//pinfo->video_format;
}
vdev->formats->type = AMBA_VIDEO_TYPE_YUV_656;//pinfo->input_type;
if (bit == ) {
vdev->formats->bits = ;
} else {
vdev->formats->bits = ;//pinfo->bit_resolution;
}
//format->sync_start = pinfo->sync_start;
printk("############### Debug: dummyfpga_get_format ##############\n");
return ;
} static int dummyfpga_init_device(struct vin_device *vdev)
{
vin_info("DUMMYFPGA Init\n");
return ;
} static struct vin_ops dummyfpga_ops = {
.init_device = dummyfpga_init_device,
.set_format = dummyfpga_set_format,
.get_format = dummyfpga_get_format,
.set_audio_mode_width_height = dummyfpga_set_audio_mode_width_height
}; /* ========================================================================== */
static int dummyfpga_drv_probe(struct ambpriv_device *ambdev)
{
struct vin_device *vdev;
int rval = ;
vdev = ambarella_vin_create_device(ambdev->name,
DECODER_GS2970, );
if (!vdev)
return -ENOMEM; vdev->intf_id = ;
vdev->dev_type = VINDEV_TYPE_DECODER;
vdev->sub_type = VINDEV_SUBTYPE_SDI;
vdev->default_mode = AMBA_VIDEO_MODE_AUTO;
vdev->default_hdr_mode = AMBA_VIDEO_LINEAR_MODE;
vdev->frame_rate = AMBA_VIDEO_FPS_AUTO; rval = ambarella_vin_register_device(vdev, &dummyfpga_ops,
dummyfpga_formats, ARRAY_SIZE(dummyfpga_formats),
dummyfpga_plls, ARRAY_SIZE(dummyfpga_plls));
if (rval < ) {
ambarella_vin_free_device(vdev);
return rval;
} ambpriv_set_drvdata(ambdev, vdev); vin_info("Dummyfpga Init, with LVDS I/F\n");
return ;
} static int dummyfpga_drv_remove(struct ambpriv_device *ambdev)
{
struct vin_device *vdev = ambpriv_get_drvdata(ambdev); ambarella_vin_unregister_device(vdev);
ambarella_vin_free_device(vdev); return ;
} static struct ambpriv_driver dummyfpga_driver = {
.probe = dummyfpga_drv_probe,
.remove = dummyfpga_drv_remove,
.driver = {
.name = "dummyfpga",
.owner = THIS_MODULE,
}
}; static int xilinx_i2c_write_4bytes(struct xilinx_dev *dev, u8 subaddr[], u8 data[])
{
int rval;
u8 pbuf[];
struct i2c_client *client = dev->client; pbuf[] = subaddr[];
pbuf[] = subaddr[];
pbuf[] = data[];
pbuf[] = data[];
pbuf[] = data[];
pbuf[] = data[]; rval = i2c_master_send(client, pbuf, );
if (rval < ) {
vin_error("addr w failed(%d): [0x%x%x]\n", rval, subaddr[], subaddr[]);
return rval;
} return ;
} static int xilinx_i2c_read_4bytes(struct xilinx_dev *dev, u8 subaddr[], u8 data[])
{
int rval;
struct i2c_msg msgs[];
struct i2c_client *client = dev->client; msgs[].len = ;
msgs[].addr = client->addr;
msgs[].flags = client->flags;
//msgs[0].buf = &subaddr[0];
msgs[].buf = subaddr; msgs[].len = ;
msgs[].addr = client->addr;
msgs[].flags = client->flags | I2C_M_RD;
//msgs[1].buf = &data[0];
msgs[].buf = data; rval = i2c_transfer(client->adapter, msgs, );
if (rval < ) {
vin_error("addr r failed(%d): [0x%x%x]\n", rval, subaddr[], subaddr[]);
return rval;
} return ;
} static int i2c_test_write(struct xilinx_dev *dev)
{
int rval;
u8 subaddr[];
u8 data[]; subaddr[] = 0x00;
subaddr[] = 0x04;
data[] = 0x12;
data[] = 0x34;
data[] = 0x56;
data[] = 0x78;
rval = xilinx_i2c_write_4bytes(dev, subaddr, data);
if (rval < )
return rval; return ;
} static int i2c_test_read(struct xilinx_dev *dev)
{
int rval;
u8 subaddr[];
u8 data[]; subaddr[] = 0x00;
subaddr[] = 0x04;
rval = xilinx_i2c_read_4bytes(dev, subaddr, data);
if (rval < )
return rval;
printk("0x0004=%02x%02x%02x%02x\n", data[], data[], data[], data[]); subaddr[] = 0x00;
subaddr[] = 0x00;
rval = xilinx_i2c_read_4bytes(dev, subaddr, data);
if (rval < )
return rval;
printk("0x0000=%02x%02x%02x%02x\n", data[], data[], data[], data[]); return ;
} static irqreturn_t xilinx_i2c_interrupt(int irq, void *dev_id)
{
struct xilinx_dev *xilinx_devp = (struct xilinx_dev *)dev_id;
i2c_test_write(xilinx_devp);
i2c_test_read(xilinx_devp); return IRQ_HANDLED;
} static const struct file_operations xilinx_fops = {
.owner = THIS_MODULE,
// .read =
// .write =
// .unlocked_ioctl =
// .open =
// .release =
}; static void xilinx_setup_cdev(struct xilinx_dev *dev, int index)
{
int err, devno = MKDEV(xilinx_cdev_major, index); cdev_init(&dev->cdev, &xilinx_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, );
if (err)
printk(KERN_NOTICE "Error %d adding xilinx_cdev%d", err, index);
} static int xilinx_i2c_proble(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
struct xilinx_dev *xilinx_devp;
struct device *dev = &client->dev;
dev_t devno = MKDEV(xilinx_cdev_major, ); printk("xilinx_i2c_probe\n");
if (xilinx_cdev_major)
ret = register_chrdev_region(devno, , "xilinx_cdev");
else {
ret = alloc_chrdev_region(&devno, , , "xilinx_cdev");
xilinx_cdev_major = MAJOR(devno);
}
if (ret < )
return ret; xilinx_devp = kzalloc(sizeof(struct xilinx_dev), GFP_KERNEL);
if (!xilinx_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
xilinx_devp->client = client;
i2c_set_clientdata(client, xilinx_devp); xilinx_setup_cdev(xilinx_devp, );
xilinx_devp->cls = class_create(THIS_MODULE, "xilinx_class");
if (IS_ERR(xilinx_devp->cls)) {
printk("Err: failed in creating xilinx cdev class\n");
return -;
}
device_create(xilinx_devp->cls, NULL, MKDEV(xilinx_cdev_major, ), NULL, "xilinx_fpga");
ret = devm_request_threaded_irq(dev, client->irq, NULL,
xilinx_i2c_interrupt, IRQF_ONESHOT,
client->name, xilinx_devp);
if (ret) {
dev_err(dev, "request irq %d failed: %d\n", client->irq, ret);
return ret;
} else
printk("request irq:%d\n", client->irq); mutex_init(&xilinx_devp->rw_mutex); //i2c_test_write(xilinx_devp);
//i2c_test_read(xilinx_devp); fail_malloc:
unregister_chrdev_region(devno, );
return ret;
} static int xilinx_i2c_remove(struct i2c_client *client)
{
struct xilinx_dev *xilinx_devp;
dev_t devno = MKDEV(xilinx_cdev_major, );
xilinx_devp = (struct xilinx_dev *)i2c_get_clientdata(client);
cdev_del(&xilinx_devp->cdev);
device_destroy(xilinx_devp->cls, devno);
class_destroy(xilinx_devp->cls);
kfree(xilinx_devp);
unregister_chrdev_region(devno, ); return ;
} static const struct i2c_device_id xilinx_idtable[] = {
{"xilinx", },
{},
};
MODULE_DEVICE_TABLE(i2c, xilinx_idtable); static const struct of_device_id xilinx_dt_ids[] = {
{.compatible = "ambarella,ambvin",},
{},
};
MODULE_DEVICE_TABLE(of, xilinx_dt_ids); static struct i2c_driver i2c_driver_xilinx = {
.driver = {
.name = "xilinx",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(xilinx_dt_ids),
//.of_match_table = xilinx_dt_ids,
},
.id_table = xilinx_idtable,
.probe = xilinx_i2c_proble,
.remove = xilinx_i2c_remove, }; static struct ambpriv_device *dummyfpga_device;
static int __init dummyfpga_init(void)
{
int rval = ; rval = i2c_add_driver(&i2c_driver_xilinx);
if (rval < ) {
printk("add xilinx i2c driver failed\n");
return rval;
} dummyfpga_device = ambpriv_create_bundle(&dummyfpga_driver, NULL, -, NULL, -);
if (IS_ERR(dummyfpga_device))
rval = PTR_ERR(dummyfpga_device);
return ;
} static void __exit dummyfpga_exit(void)
{
i2c_del_driver(&i2c_driver_xilinx);
ambpriv_device_unregister(dummyfpga_device);
ambpriv_driver_unregister(&dummyfpga_driver);
} module_init(dummyfpga_init);
module_exit(dummyfpga_exit); MODULE_DESCRIPTION("dummyfpga decoder");
MODULE_LICENSE("GPL");

驱动加载后,切换视频源制式,中断会触发,在dmesg里会看到中断处理函数里对0x0004寄存器的读写结果。

就先介绍到这里吧,ambarella H2平台坑真的很多,希望你们在以后的开发过程中不会用到这个平台的东西,多用用海思平台,支持国产,O(∩_∩)O哈哈~

ambarella H2平台fpga捕捉卡驱动案例的更多相关文章

  1. amba H2平台用PWM控制LCD背光

    ambarella H2系列Soc的GPIO口能作PWM使用的个数有限(GPIO0-GPIO3),从PRM里GPIO: Function Selection章节可以得到如何配置GPIO为PWM功能. ...

  2. 芯灵思SinlinxA33开发板 Linux平台总线设备驱动

    1.什么是platform(平台)总线? 相对于USB.PCI.I2C.SPI等物理总线来说,platform总线是一种虚拟.抽象出来的总线,实际中并不存在这样的总线. 那为什么需要platform总 ...

  3. Linux平台总线设备驱动

    1. 平台总线(Platform bus)是linux2.6内核加入的一种虚拟总线,其优势在于采用了总线的模型对设备(没有挂到真实总线的设备)与驱动进行了管理,这样提高了程序的可移植性. 2. 平台总 ...

  4. Android平台开发-WIFI 驱动移植 -- 详细

    一.WIFI的基本架构(代码路径)     1.WIFI Settings应用程序:       packages/apps/Settings/src/com/android/settings/wif ...

  5. 嵌入式Linux驱动案例之中的一个

    前几天解决一个嵌入式Linux驱动问题,做为一个案例进行记录. 本案例是一个CPU通过LocalBus总线訪问外围一个设备,详细设备是一个DSP器件.在实际应用中,性能要求非常高,对数据訪问速度提出比 ...

  6. Java版 家政服务 社区服务 家装服务平台 源码 有案例 可定制

    产品说明: 家装服务平台.社区服务平台.服务类型的平台--公司成熟产品 包括工匠注册.资质认证.发布服务产品.会员注册.预约服务.工匠定价.执行服务.服务完毕填写工作日志上传现场照片.会员确认服务.返 ...

  7. ambarella H2 添加文件到ext4文件系统

    方法1: ambarella/rootfs目录下有skeleton(骨架)目录,此目录下就是文件系统的各个目录, [root@jz4775dev]# ls skeleton/ bin debug de ...

  8. 案例分析:大数据平台技术方案及案例(ppt)

    大数据平台是为了计算,现今社会所产生的越来越大的数据量,以存储.运算.展现作为目的的平台.大数据技术是指从各种各样类型的数据中,快速获得有价值信息的能力.适用于大数据的技术,包括大规模并行处理(MPP ...

  9. USB-Blaster CPLD FPGA Intel 驱动安装不上的问题,文件的哈希值不在指定的目录文件中,的解决办法,其实很简单

    intel的官网的驱动安装文档: https://www.intel.com/content/www/us/en/programmable/support/support-resources/down ...

随机推荐

  1. coding++:Spring Boot 全局事务解释及使用(二)

    什么是全局事务: Spring Boot(Spring) 事务是通过 aop(aop相关术语:通知(Advice).连接点(Joinpoint).切入点(Pointcut).切面(Aspect).目标 ...

  2. 图的深度优先搜索dfs

    图的深度优先搜索: 1.将最初访问的顶点压入栈: 2.只要栈中仍有顶点,就循环进行下述操作: (1)访问栈顶部的顶点u: (2)从当前访问的顶点u 移动至顶点v 时,将v 压入栈.如果当前顶点u 不存 ...

  3. Mongodb中 数据库和集合的创建与删除

    1.查询数据库,查询表: show dbs //查询所有的数据库show collections //查询所有的集合(表) 2.创建数据库或切换到数据库(存在就切换,不存在就创建) use spide ...

  4. Infrared-Visible Cross-Modal Person Re-Identification with an X Modality (AAAI 2020)

    Infrared-Visible Cross-Modal Person Re-Identification with an X Modality (AAAI 2020) 1. Motivation 可见 ...

  5. 华为五年自动化测试工程详细解说:unittest单元测试框架

    一.单元测试框架说明 ​ 单元测试是指在编程中,针对程序模块的最小单元(类中的方法)进行正确性检验的测试工作.python+selenium自动化测试中通常使用unittest或者pytest作为单元 ...

  6. Appium自动化 - 设置unicodeKeyboard: True运行脚本后,手机输入时无法调出软键盘

    问题背景 做appium自动化的时候,使用了UiAutomator1驱动,然后设置了UnicodeKeyboard 执行自动化脚本之后,玩手机的时候发现平时用的输入法键盘没法调出来了 'automat ...

  7. 1046 Shortest Distance (20分)

    The task is really simple: given N exits on a highway which forms a simple cycle, you are supposed t ...

  8. .NET Core项目部署到Linux(Centos7)(六)发布.NET Core 项目到Linux

    目录 1.前言 2.环境和软件的准备 3.创建.NET Core API项目 4.VMware Workstation虚拟机及Centos 7安装 5.Centos 7安装.NET Core环境 6. ...

  9. .NET Core项目部署到Linux(Centos7)(九)防火墙配置,允许外网或局域网访问.NET Core站点

    目录 1.前言 2.环境和软件的准备 3.创建.NET Core API项目 4.VMware Workstation虚拟机及Centos 7安装 5.Centos 7安装.NET Core环境 6. ...

  10. .NET Core项目部署到Linux(Centos7)(三)创建.NET Core API项目

    目录 1.前言 2.环境和软件的准备 3.创建.NET Core API项目 4.VMware Workstation虚拟机及Centos 7安装 5.Centos 7安装.NET Core环境 6. ...