嵌入式Linux驱动开发日记
目录
让我们从环境配置开始
目标平台
从Ubuntu开始
从交叉编译器继续
arm-linux-gnueabihf-gcc
vscode
没学过ARM汇编
正文开始——速度体验一把
写一个链接脚本
写一个简单的Makefile脚本
使用正点原子的imxdownload下载到自己的SD卡上
更进一步的笔记和说明
从IMX6ULL启动方式说起
IMX6ULL的启动方式
定制合法的可烧写识别的合法执行文件
关于点灯本身
让我们从环境配置开始
当你看到这篇文章的时候,说明你已经准备好开始嵌入式Linux开发之旅了。这篇博客也是笔者自己学习嵌入式Linux开发的日记。希望自己可以坚持到写完这个博客笔记,通关一次简单的嵌入式Linux开发。
目标平台
笔者使用的是正点原子Alpha开发板,上面是Cortex-A7的IMX6ULL的芯片,主频啥的谈不上高,但是学习足够了
笔者使用的是Ubuntu24.04(其实别的也行)
从Ubuntu开始
起始从哪里开始都行,笔者这里为了少折腾,选择从Ubuntu开始。笔者安装的在了Arch主机上的双系统上,具体如何操作这里不是重点。你可以跟正点原子一样欢乐虚拟机,但是我寻思着太麻烦了。
把Ubuntu配成自己喜欢的样子(包括但不限于输入法,基本的vscode,shell自定义,桌面自定义,代理等),基本上就可以欢欢乐乐的开始嵌入式Linux开发了。
关于如何配置Ubuntu,可以自己上网找博客看,在这里的一般都不会对后面的开发造成实质的影响。
从交叉编译器继续
实际上笔者之前玩过交叉编译器,简单的讲讲啥是交叉编译器,这个东西对于我们嵌入式开发者而言会天天见到。笔者建议好好熟悉了解相关的概念。
首先,你现在电脑上(哦!也有可能是虚拟机!)默认的自带的gcc是在x64上跑的,编写的程序是给x64架构的程序使用的编译器。诚然,我们开发肯定还是在x64架构的机器上搞开发。但是我们现在要面向的平台毫无疑问的是ARM架构的机器。那怎么办呢?
这个时候就需要请出我们的交叉编译器了。
arm-linux-gnueabihf-gcc
是的!这是我们要使用的gcc编译器。它隶属于arm-linux交叉编译器的大家族
首先,交叉编译器的命名规则是arch [-vendor] [-os] [-(gnu)eabi] [-language]
-
arch - 体系架构, 如arm(ARM-32bit)、aarch64(ARM-64bit)、x86等;
-
vendor -工具链提供商,经常省略,或用 none 替代;
-
os - 目标操作系统, 如linux,没针对具体 os 则 用 none 替代。同时没有 vendor 和os 使用一个 none 替代。
-
eabi - 嵌入式应用二进制接口(Embedded Application binary Interface)
-
language - 编译语言,如gcc,g++
那事情就很简单的了:这个是面向arm架构的,给嵌入式二进制armhf接口生成跨平台程序的gcc(当然,还有其他的比如说arm-none-eabi-gcc,gcc-arm-linux-gnueabi等等,他们的区别不算大,但是值得你去根据你使用的机器查到底用哪个版本的gcc),如果你跟我一样,使用的是面向CortexA7的IMX6ULL进行开发,你所干的事情很简单。
sudo apt install gcc-arm-linux-gnueabihf
啥?你说正点原子告诉你Linaro那里去下?拜托,早就不维护了那个地址。当然如果你担心你没法排查问题,用老壁灯gcc4没问题,但是你确定你这是在学习吗?之后的开发出问题了你又该咋办?
现在,你啥也不用做,直接
arm-linux-gnueabihf-gcc -v
在笔者这个时间,你会高兴的得到13.2的超级高版本的gcc!现在开始你也是现代的1嵌入式Linux开发人员了!
vscode
嗯,直接安装vscode就行了,啥?你说你跟着的是Ubuntu16.04?非常坏老铁,这年头没几个人用咯,就跟着正点原子走吧。在24.04,安装vscode就是sudo apt install code一句话搞定的事情。
没学过ARM汇编
正文开始——速度体验一把
速速开始正文。
我们将会把代码烧到SD卡上,然后操控板子使用SD卡启动启动到内存里去。裸机程序,没有操作系统,你可能就会想起STM32的开发了。是的,我们需要起手写startup,放在DCD初始化结束后的位置上,IMX6ULL芯片执行结束后会跳转到地址0x87800000的地方,所以,我们需要在那个地方安排上我们的启动程序。
一个startup其实可能比你想象的要简单。
// start up files for led .global _start _start: /* GCC always find symbols in _start for the entry */mrs r0, cpsr/*该操作清除了 r0 中最低的 5 位,即将处理器模式和相关标志清除*/bic r0, r0, #0x1f/*下面这条指令执行按位或操作。它将 r0 中的内容与 0x13(即二进制的 10011)进行按位或操作,并将结果存回 r0。0x13 代表了设置 CPSR 中的模式位为 0x13(对应用户模式)。*/orr r0, r0, #0x13msr cpsr, r0// load to the main and init the sp // in this way :)ldr sp, =0x80200000b main
啊哈!你说奇怪,系统时钟,中断向量表嘞?放心,早初始化完了,你需要做的事情很简单。就是:
-
修改我们的处理器SVC模式,办法是取出CPSR寄存器的值,对之进行修改,然后放回去!简单吧!
-
然后设置我们的栈指针指向更高的2MB地址出(说明我们给程序2MB的地址空间大小,足够!)
-
跳转道我们后面写的main函数里去。
后面我们的main函数也不难:
#ifndef __MAIN_H #define __MAIN_H /* * CCM相关寄存器地址 */ #define CCM_CCGR0 *((volatile unsigned int *)0X020C4068) #define CCM_CCGR1 *((volatile unsigned int *)0X020C406C) #define CCM_CCGR2 *((volatile unsigned int *)0X020C4070) #define CCM_CCGR3 *((volatile unsigned int *)0X020C4074) #define CCM_CCGR4 *((volatile unsigned int *)0X020C4078) #define CCM_CCGR5 *((volatile unsigned int *)0X020C407C) #define CCM_CCGR6 *((volatile unsigned int *)0X020C4080) /* * IOMUX相关寄存器地址 */ #define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068) #define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4) /* * GPIO1相关寄存器地址 */ #define GPIO1_DR *((volatile unsigned int *)0X0209C000) #define GPIO1_GDIR *((volatile unsigned int *)0X0209C004) #define GPIO1_PSR *((volatile unsigned int *)0X0209C008) #define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C) #define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010) #define GPIO1_IMR *((volatile unsigned int *)0X0209C014) #define GPIO1_ISR *((volatile unsigned int *)0X0209C018) #define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C) #endif
这里是函数的主体,如果您想尝试,直接复制就好。
#include "main.h" void enable_clk() {CCM_CCGR0 = 0xffffffff;CCM_CCGR1 = 0xffffffff;CCM_CCGR2 = 0xffffffff;CCM_CCGR3 = 0xffffffff;CCM_CCGR4 = 0xffffffff;CCM_CCGR5 = 0xffffffff;CCM_CCGR6 = 0xffffffff; } void led_init() {SW_MUX_GPIO1_IO03 = 0x5;SW_PAD_GPIO1_IO03 = 0x10B0;GPIO1_GDIR = 0x00000008;GPIO1_DR = 0x0; } void led_on() {// set the forth bit as 0GPIO1_DR &= ~(1 << 3); } void led_off() {GPIO1_DR |= (1 << 3); } void delay_in_n_nops(volatile unsigned int n){while(n--){} } void delay_ms(volatile unsigned int n){while(n--){delay_in_n_nops(0x7ff);} } int main() {enable_clk();led_init();while(1){led_on();delay_ms(500); led_off();delay_ms(500);} return 0; // cheats anyway :) }
后面我会一个个结束,到这里就OK。
写一个链接脚本
链接脚本是啥,也先不着急:
SECTIONS{. = 0x87800000;.text : {start.oled.o*(.text)}.rodata ALIGN(4) : {*(.rodata*)}.data ALIGN(4) : {*(.data*)}__bss_start = . ;.bss ALIGN(4) : {*(.bss) *(COMMON)}__bss_end = . ; }
写一个简单的Makefile脚本
objs := start.o led.o ARCH_PREFIX := arm-linux-gnueabihf CC := gcc LD := ld OBJDUMP := objdump OBJCOPY := objcopy RESULT_NAME := led ENTRY_SCRIPT := link.lds ${RESULT_NAME}.bin:$(objs)${ARCH_PREFIX}-${LD} -T$(ENTRY_SCRIPT) -o ${RESULT_NAME}.elf $^${ARCH_PREFIX}-${OBJCOPY} -O binary -S ${RESULT_NAME}.elf $@${ARCH_PREFIX}-${OBJDUMP} -D -m arm ${RESULT_NAME}.elf > ${RESULT_NAME}.dis %.o: %.s ${ARCH_PREFIX}-${CC} -Wall -nostdlib -c -o $@ $< %.o: %.S${ARCH_PREFIX}-${CC} -Wall -nostdlib -c -o $@ $< %.o: %.c${ARCH_PREFIX}-${CC} -Wall -nostdlib -c -o $@ $< clean:rm -rf *.o ${RESULT_NAME}.bin ${RESULT_NAME}.elf ${RESULT_NAME}.dis
这是一种Makefile脚本的写法,当然不止这一种!你可以按照这个层级把文件排放:
现在直接make得到我们的bin文件。
使用正点原子的imxdownload下载到自己的SD卡上
下一步是使用imxdownload将我们的bin文件下载到我们的SD卡上,这个imxdownload文件是正点原子提供的,实际上就是组合我们的头部信息进一步生成可以被板子执行的文件。
现在我们需要做的就是插上SD卡。查看自己的SD卡被分配的分区。一般的/dev/sda是自己的电脑的磁盘,嗯,一种最好的方式是查看自己的dmesg输出信息。
sudo dmesg | tail -n 20这个指令将会打印出内核日志文件的倒数二十行它一般会记载着我们新USB设备挂载的信息
[ 3843.237182] usb 3-1: New USB device strings: Mfr=1, Product=3, SerialNumber=2 [ 3843.237188] usb 3-1: Product: Mass Storage Device [ 3843.237192] usb 3-1: Manufacturer: Generic [ 3843.237195] usb 3-1: SerialNumber: 121220160204 [ 3843.241247] usb-storage 3-1:1.0: USB Mass Storage device detected [ 3843.241710] scsi host6: usb-storage 3-1:1.0 [ 3844.243574] scsi 6:0:0:0: Direct-Access Mass Storage Device 1.00 PQ: 0 ANSI: 0 CCS [ 3844.244224] sd 6:0:0:0: Attached scsi generic sg2 type 0 [ 3844.477530] sd 6:0:0:0: [sdb] 122138624 512-byte logical blocks: (62.5 GB/58.2 GiB) [ 3844.477668] sd 6:0:0:0: [sdb] Write Protect is off [ 3844.477672] sd 6:0:0:0: [sdb] Mode Sense: 03 00 00 00 [ 3844.477826] sd 6:0:0:0: [sdb] No Caching mode page found [ 3844.477829] sd 6:0:0:0: [sdb] Assuming drive cache: write through [ 3844.479862] sdb: sdb1 [ 3844.480015] sd 6:0:0:0: [sdb] Attached SCSI removable disk
所以,笔者得到的目标文件是向/dev/sdb写东西!
注意,使用imxdownload将会完全覆盖里面原本的东西,请做好备份再写!
请注意!确保自己的SD卡挂载的位置!不要写错地方了,操作时不可逆的。正点原子之前提供的办法是插拔反复对比,这个办法不好但是总归管用。
发现你的终端不自动补全imxdownload,请修改文件权限为744,即对自己完全可用,其他人只有读权限的文件!
./imxdownload led.bin /dev/sdb
输入密码后,观察写入速率,一般而言是一百多到几百KB每秒,大于这个速度上MB的一概认为是读写失败,重新拔下来看看自己的sd卡有没有接好。
现在调整板子的启动模式是SD卡启动,如下所示:
现在,你的板子上电,应该可以看到现象是:
是的,DS0标记上的灯会闪烁。这就是现象!
更进一步的笔记和说明
如果你并不打算进一步仔细学习,到这里就可以走了,后面的内容比较的硬核。
从IMX6ULL启动方式说起
首先,我们不在乎外面的构建问题,从代码编写入手。
IMX6ULL的启动方式
BOOT 的处理过程是发生在I.MX6U 芯片上电以后,芯片会根据 BOOT_MODE[1:0]的设置来选择 BOOT 方式。换而言之,你的板子的BOOT开关如何,深刻的决定了你的板子是如何启动的。
笔者的板子默认是从EMMC启动的,那里存放着默认的Linux操作系统。当然这是后面笔者需要学习的
所以你可以看到,我们是把 BOOT_MODE1 为 1,BOOT_MODE0 为 0 的时候此模式使能,在此模式下,芯片会执行内部的 boot ROM 代码,这段 boot ROM 代码会进行硬件初始化(一部分外设),然后从 boot 设备(就是存放代码的设备、比如 SD/EMMC、NAND)中将代码拷贝出来复制到指定的 RAM 中,一般是 DDR。
我们就是想要设置SD卡启动,一次来看,那就是对我们的1号和7号拨码开关拉高到ON就OK了,这就是我们上面给出的截图。具体的内容可以查手册的Boot内容
定制合法的可烧写识别的合法执行文件
-
Image vector table,简称 IVT,IVT 里面包含了一系列的地址信息,这些地址信息在ROM 中按照固定的地址存放着。
-
Boot data,启动数据,包含了镜像要拷贝到哪个地址,拷贝的大小是多少等等。
-
Device configuration data,简称DCD,设备配置信息,重点是 DDR3 的初始化配置。
-
用户代码可执行文件,比如 led.bin。
实际上就是四个部分组成!可以看出最终烧写到 I.MX6U 中的程序其组成为:IVT+Boot data+DCD+.bin。所以所生成的 load.imx 就是在 led.bin 前面加上 IVT+Boot data+DCD。内部 Boot ROM 会将 load.imx 拷贝到 DDR 中,用户代码是要一定要从 0X87800000 这个地方开始的,因为链接地址为 0X87800000,load.imx 在用户代码前面又有 3KByte 的 IVT+Boot Data+DCD 数据,因此 load.imx 在 DDR 中的起始地址就是 0X87800000-3072=0X877FF400。
load.imx 最前面的就是 IVT 和 Boot Data,IVT 包含了镜像程序的入口点、指向 DCD 的指针和一些用作其它用途的指针。内部 Boot ROM 要求IVT 应该放到指定的位置,不同的启动设备位置不同,而 IVT 在整个 load.imx 的最前面,其实就相当于要求 load.imx 在烧写的时候该烧写到存储设备的指定位置去。整个位置都是相对于存储设备的起始地址的偏移
IVT部分存者的就是这些
首先是头部信息:格式如上!
复位以后,I.MX6U 片内的所有寄存器都会复位为默认值,但是这些默认值往往不是我们想要的值,而且有些外设我们必须在使用之前初始化它。为此 I.MX6U 提出了一个 DCD(Device Config Data)的概念,和 IVT、Boot Data 一样,DCD 也是添加到 load.imx 里面的,紧跟在 IVT和 Boot Data 后面,IVT 里面也指定了DCD 的位置。DCD 其实就是 I.MX6U 寄存器地址和对应的配置信息集合,Boot ROM 会使用这些寄存器地址和配置集合来初始化相应的寄存器,比如开启某些外设的时钟、初始化 DDR 等等。DCD 区域不能超过 1768Byte,
其中Tag 是单字节,固定为0XD2,Length 为两个字节,表示DCD 区域的大小,包含header,同样是大端模式,Version 是单字节,固定为 0X40 或者 0X41。
header的结构还是类似的。CMD的格式如下:
这里的Tag 为一个字节,固定为 0XCC。Length 是两个字节,包含写入的命令数据长度,包含 header,同样是大端模式。Parameter 为一个字节
①、设置CCGR0~CCGR6 这 7 个外设时钟使能寄存器,默认打开所有的外设时钟。 ②、配置DDR3 所用的所有IO。 ③、配置MMDC 控制器,初始化DDR3。 就是干这些事情。正点原子的imxdownload也是实际上完成这个工作。
这些地址需要在手册中查出来。一一填写。这个是搬运工的活,具体可以看
比如说这里,我们可以查到这个CCGR0寄存器映射地址是0x020C4068等等。看官可以查看我们的imxdownload.h看看具体的数值对应着查,体会一下查手册的快乐(不是)
关于点灯本身
实际上就是在操作寄存器!我们需要关心的寄存器,其实需要去手册上查。比如说根据原理图,我们发现想要驱动我们的LED0,就需要我们找寄存器:SW_MUX_GPIO1_IO03和SW_PAD_GPIO1_IO03。在手册中,我们需要寻找的就是SW_MUX_CTL_PAD_GPIO1_IO03 SW MUX ControlRegister (IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03)和IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03寄存器(Man!找了很久的手册!)
现在只需要查询手册依照配置就好了!完事了就是上面的程序!
相关文章:
嵌入式Linux驱动开发日记
目录 让我们从环境配置开始 目标平台 从Ubuntu开始 从交叉编译器继续 arm-linux-gnueabihf-gcc vscode 没学过ARM汇编 正文开始——速度体验一把 写一个链接脚本 写一个简单的Makefile脚本 使用正点原子的imxdownload下载到自己的SD卡上 更进一步的笔记和说明 从IM…...
迪杰特斯拉算法(Dijkstra‘s)
迪杰斯特拉算法(Dijkstras algorithm)是由荷兰计算机科学家艾兹格迪科斯彻(Edsger W. Dijkstra)在1956年提出的,用于在加权图中找到单个源点到所有其他顶点的最短路径的算法。这个算法广泛应用于网络路由、地图导航等领…...
reids基础
数据结构类型 String setnx //设置key不存在,则添加成功 setex name 10 jack // key 10s失效,自动删除 hash hset hget list 按添加数据排序 lpush //左侧插入 rpush //右侧插入 set 不重复 sadd //添加…...
私有化部署视频平台EasyCVR宇视设备视频平台如何构建视频联网平台及升级视频转码业务?
在当今数字化、网络化的时代背景下,视频监控技术已广泛应用于各行各业,成为保障安全、提升效率的重要工具。然而,面对复杂多变的监控需求和跨区域、网络化的管理挑战,传统的视频监控解决方案往往显得力不从心。 EasyCVR视频融合云…...
SparkContext讲解
SparkContext讲解 什么是 SparkContext? SparkContext 是 Spark 应用程序的入口点,是 Spark 的核心组件之一。每个 Spark 应用程序启动时,都会创建一个 SparkContext 对象,它负责与集群管理器(如 YARN、Mesos 或 Spa…...
MODBUS TCP转CANOpen网关
Modbus TCP转CANopen网关 型号:SG-TCP-COE-210 产品用途 本网关可以实现将CANOpen接口设备连接到MODBUS TCP网络中;并且用户不需要了解具体的CANOpen和Modbus TCP 协议即可实现将CANOpen设备挂载到MODBUS TCP接口的 PLC上,并和CANOpen设备…...
渗透测试---shell(4)脚本与用户交互以及if条件判断
声明:学习素材来自b站up【泷羽Sec】,侵删,若阅读过程中有相关方面的不足,还请指正,本文只做相关技术分享,切莫从事违法等相关行为,本人一律不承担一切后果 目录 一、shell脚本与用户进行交互 使用 read 指…...
02_Spring_IoC实现
接下来先简单说一下关于IoC的一些要点,后面我们再详细一步一步讨论。 一、IoC控制反转 IoC控制反转它是一种思想,不是具体的实现控制反转的目的是为了降低程序的耦合度,提高程序的可扩展性,从而满足OCP原则和DIP原则控制反转,那到底反转是什么东西? 我们不再使用某个对象…...
使用Python3实现Gitee码云自动化发布
仓库信息 https://gitee.com/liumou_site/ip 实现代码 import osimport requests from loguru import loggerdef gitee(ver, message, prerelease: bool False):"""在 Gitee 上创建发布版本:param ver: 版本号:param message: 发布信息:param prerelease: 是…...
Ubuntu24.04下的docker问题
按官网提示是可以安装成功的,但是curl无法使用https下载,会造成下述语句执行失败 # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https…...
PAT (Basic Level) Practice (中文)1002 写出这个数
读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字。 #include<bits/stdc.h> using namespace std; string a; int sum0; int f0; int n[10005]; int main(){ cin>>a; int c0; int laa.size(); for(int i…...
C07.L07.STL之映射.应用2.统计数字
题目描述 某次科研调查时得到了 n 个自然数,每个数均不超过 1500000000 (1.5*10^9 )。已知不相同的数不超过 10000 个,现在需要统计这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果。 输入格式 包含 2 行: 第…...
微信小程序组件详解:text 和 rich-text 组件的基本用法
微信小程序组件详解:text 和 rich-text 组件的基本用法 引言 在微信小程序的开发中,文本展示是用户界面设计中不可或缺的一部分。无论是简单的文本信息,还是复杂的富文本内容,text 和 rich-text 组件都能够帮助我们实现这些需求。本文将详细介绍这两个组件的基本用法,包…...
算法.图论-习题全集(Updating)
文章目录 本节设置的意义并查集篇并查集简介以及常见技巧并查集板子(洛谷)情侣牵手问题相似的字符串组岛屿数量(并查集做法)省份数量移除最多的同行或同列石头最大的人工岛找出知晓秘密的所有专家 建图及其拓扑排序篇链式前向星建图板子课程表 本节设置的意义 主要就是为了复习…...
this.$prompt 限制输入长度
this.$prompt(请输入关键词名称, 提示, {confirmButtonText: 确定,cancelButtonText: 取消,inputPattern: /^\d{0,50}$/,inputErrorMessage: 关键词名称长度不能超过50个字符 }).then(({ value }) > {})...
JDBC使用p6spy记录实际执行SQL方法【解决SQL打印两次问题】
p6spy介绍 p6spy 是一个开源的 JDBC 数据源代理工具,主要用于拦截和记录应用程序与数据库之间的所有 SQL 操作,方便开发者进行 SQL 调试、性能监控和问题排查。 p6spy可以打印实际执行的sql,在开发过程中方便调试,和使用框架无关…...
问题: redis-高并发场景下如何保证缓存数据与数据库的最终一致性
在高并发场景下,Redis 通常用作缓存层,与数据库结合使用以提高系统的性能。为了保证缓存数据与数据库的最终一致性,通常采用的有双写机制、缓存失效机制,基于双写机制、缓存失效机制又衍生出来了消息队列、事件驱动架构等 常见机…...
Stable Diffusion核心网络结构——CLIP Text Encoder
🌺系列文章推荐🌺 扩散模型系列文章正在持续的更新,更新节奏如下,先更新SD模型讲解,再更新相关的微调方法文章,敬请期待!!!(本文及其之前的文章均已更新&…...
C语言-11-18笔记
1.C语言数据类型 类型存储大小值范围char1 字节-128 到 127 或 0 到 255unsigned char1 字节0 到 255signed char1 字节-128 到 127int2 或 4 字节-32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647unsigned int2 或 4 字节0 到 65,535 或 0 到 4,294,967,295short2 字节…...
数据结构_图的遍历
深度优先搜索遍历 遍历思想 邻接矩阵上的遍历算法 void Map::DFSTraverse() {int i, v;for (i 0; i < MaxLen; i){visited[i] false;}for (i 0; i < Vexnum; i){// 如果顶点未访问,则进行深度优先搜索if (visited[i] false){DFS(i);}}cout << endl…...
设计LRU缓存
LRU缓存 LRU缓存的实现思路LRU缓存的操作C11 STL实现LRU缓存自行设计双向链表 哈希表 LRU(Least Recently Used,最近最少使用)缓存是一种常见的缓存淘汰算法,其基本思想是:当缓存空间已满时,移除最近最少使…...
python中的base64使用小笑话
在使用base64的时候将本地的图片转换为base64 代码如下,代码绝对正确 import base64 def image_to_data_uri(image_path):with open(image_path, rb) as image_file:image_data base64.b64encode(image_file.read()).decode(utf-8)file_extension image_path.sp…...
Python之time时间库
time时间库 概述获取当前时间time库datetime库区别 时间元组处理获取时间元组的各个部分时间戳和时间元组的转换 格式化时间格式化时间解析时间格式符号说明 暂停程序计时操作简单计时高精度计时计时器类的实现 UTC时间操作time库datetime库 概述 time是Python标准库中的一个模…...
Easyexcel(4-模板文件)
相关文章链接 Easyexcel(1-注解使用)Easyexcel(2-文件读取)Easyexcel(3-文件导出)Easyexcel(4-模板文件) 文件导出 获取 resources 目录下的文件,使用 withTemplate 获…...
国产linux系统(银河麒麟,统信uos)使用 PageOffice 动态生成word文件
PageOffice 国产版 :支持信创系统,支持银河麒麟V10和统信UOS,支持X86(intel、兆芯、海光等)、ARM(飞腾、鲲鹏、麒麟等)、龙芯(LoogArch)芯片架构。 数据区域填充文本 数…...
Window11+annie 视频下载器安装
一、ffmpeg环境的配置 下载annie之前需要先配置ffmpeg视频解码器。 网址下载地址 https://ffmpeg.org/download.html1、在网址中选择window版本 2、点击后选择该版本 3、下载完成后对压缩包进行解压,后进行环境的配置 (1)压缩包解压&#…...
SAP GR(Group Reporting)配置篇(七)
1.7、合并处理的配置 1.7.1 定义方法 菜单路径 组报表的SAP S4HANA >合并处理的配置>定义方法 事务代码 SPI4...
共建智能软件开发联合实验室,怿星科技助力东风柳汽加速智能化技术创新
11月14日,以“奋进70载,智创新纪元”为主题的2024东风柳汽第二届科技周在柳州盛大开幕,吸引了来自全国的汽车行业嘉宾、技术专家齐聚一堂,共襄盛举,一同探寻如何凭借 “新技术、新实力” 这一关键契机,为新…...
优化表单交互:在 el-select 组件中嵌入表格显示选项
介绍了一种通过 el-select 插槽实现表格样式数据展示的方案,可更直观地辅助用户选择。支持列配置、行数据绑定及自定义搜索,简洁高效,适用于复杂选择场景。完整代码见GitHub 仓库。 背景 在进行业务开发选择订单时,如果单纯的根…...
每日一题 LCR 079. 子集
LCR 079. 子集 主要应该考虑遍历的顺序 class Solution { public:vector<vector<int>> subsets(vector<int>& nums) {vector<vector<int>> ans;vector<int> temp;dfs(nums,0,temp,ans);return ans;}void dfs(vector<int> &…...
安徽网站建设天锐科技/北京自动网络营销推广
VScode配置C/C编译环境1、下载VScode2、安装cpptools3、下载MinGW4、配置环境变量5、修改C/C配置文件5.1、编辑 launch.json 配置文件5.2、编辑 tasks.json 文件6、运行7、其他1、下载VScode 下载链接: VScode 安装过程:一路下一步,安装很简…...
深圳网站建设哪家公司便宜/腾讯广告推广平台入口
page对象是puppeteer最常用的对象,它可以认为是chrome的一个tab页,主要的页面操作都是通过它进行的。Google的官方文档详细介绍了page对象的使用,这里我只是简单的小结一下。 客户端模拟 页面模拟设置相关函数有如下几个, page.se…...
免费建站网站一级大录像不卡在线看网页/百度搜索首页
NSObject是大多数Objective-C类的继承的根类;它没有父类。通过NSObject,其它类继承了一些基础的与Objective-C语言编译器系统之间的接口,并且获得了在它的实例中表现为一个对象的能力。以下是NSObject类的继承图: 尽管NSObject不是…...
永宝网站建设招聘信息/跨境电商平台排行榜前十名
目前好多网站上应用的转盘抽奖程序大多是基于flash的,而本文结合实例将使用jQuery和PHP来实现转盘抽奖程序,为了便于理解,作者分两部分来讲解,本文讲解第一部分,侧重使用jQuery实现转盘的转动效果。第二部分侧重使用PH…...
做网站linux主机/seo排名优化软件
在网站根目录下新建一个.htaccess文件即可,编辑如下 RewriteEngine On #游戏列表详细介绍 RewriteRule ^g-([0-9]).html$ game.php?actiongamelist_desc&game_id$1 #游戏开服表 RewriteRule ^serverlist-([0-9]).html$ website.php?actionserver_list&gam…...
前程无忧招聘网/seo全网营销公司
一、什么是Navicat?Navicat是一套快速、可靠并价格相当便宜的数据库管理工具,Navicat 是以直觉化的图形用户界面而建的,让你可以以安全并且简单的方式创建、组织、访问并共用信息。这是一套快速、可靠并价格相宜的数据库管理工具,…...