嵌入式学习笔记

FreeRTOS
=========
    以下内容全部基于ARM Cotex-M系统
    * MCU的NVIC分组必须是第4组,这意味着所有中断的preemption priority是[0, 15],sub priority恒为0
    * LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY是调用FreeRTOS中断API的中断最大优先级。换句话说,所有调用FreeRTOS中断API的中断的优先级都必须大于等于这个值


GIT
====
    git log
    ========
        git log --oneline   # 每个提交只显示一行
        git log -n          # 查看最近n个提交
        git log --stat      # 每个提交显示修改的文件以及增删行数
        git log --graph [--all]     # 用ascii字符显示分支树。--all是显示所有分支

    子模块
    ======
        添加子模块:
            git submodule add <仓库地址> <本地路径>        # 这会生成.gitmodules文件。然后你就可以push到github了

        获取子模块:
            # 当你git clone时,子模块只有目录而已,你得执行下面两条命令才行
            git submodule init
            git submodule update

        删除子模块:
            rm -rf 子模块目录 删除子模块目录及源码
            vi .gitmodules 删除项目目录下.gitmodules文件中子模块相关条目
            vi .git/config 删除配置项中子模块相关条目
            rm .git/module/* 删除模块下的子模块目录,每个子模块对应一个目录,注意只删除对应的子模块目录即可

        注意:
            * 获取到子模块后,子模块里是没有任何分支的,建议每个子模块都新建一个分支
            * 如果改了子模块而不推送子模块的话,子模块的远程仓库是不更新的          


    补丁
    ====
        生成补丁:
            git diff [旧ID] [新ID] >        # 注意[旧ID]和[新ID]别写反了
            git format-patch -n -o XXX.patch  # 生成SHA1为ID的提交相较之前n个提交的所有补丁。n如果是1就是相较于相邻的上一个提交;-o指定补丁存放路径

        检查补丁能否打上:
            git apply --check

        应用补丁:
            git apply
            git am

        注意:
            * 关于组合问题:
                * git apply一般对应于git diff。这个比较简单,就跟unix的diff差不多,它会把补丁中的内容应用到工作区但不提交,你得手动提交
                * git format-patch一般对应于git am。这两个操作会把提交信息一并打入,比git diff + git apply更加智能
                * 其他组合我没试过

    git fetch
    ==========
        # 有时你需要检查当前仓库是否是最新的,但你又不想用git pull,因为你想先看一下远程仓库改了哪些,这时你可以用git fetch
        git fetch origin master:tmp     # 把远程仓库的master分支复制到本地的tmp分支
        git diff tmp                    # 查看远程仓库作了哪些改动
        git pull 或者 git merge tmp     # 合并分支
        git branch -d tmp               # 删除临时分支

    解决中文乱码
    ==========
        git config --global core.quotepath false

    栈(stash)
    ===========
        注意:"git stash"仅仅是把工作区和仓库不同的部分压入栈中,已经压栈的代码相当于仓库的代码。例如:
              $ echo "" > demo.txt
              $ echo "1" >> demo.txt
              $ git stash save "V1"
              $ echo "2" >> demo.txt
              $ git stash save "V2"
              此时如果你git stash pop,那么V2这个栈就会恢复,然而demo.txt仅仅是"2"而已,不是"1\n2"

        git stash                   # 把工作区的代码存入栈中
        git stash save "注释"       # 类似于git stash,但是可是注释

        git stash list              # 列出栈
        git stash show              # 显示栈中的代码和工作区的区别

        git stash pop               # 把栈中的代码恢复到工作区,并清除栈
        git stash apply             # 把栈中的代码恢复到工作区,但是不清除栈

        git stash clear             # 清除所有的栈
        git stash drop + 名称       # 删除一个栈

    提交
    ====
        git commit    # git会调用vim来让你输入信息,适合较多信息输入的场合
        git commit -a 指的是把当前工作目录中没有被git add添加的文件也提交到本地仓库,不推荐这个选项,应该先git add -A在git commit [-m]

        删除指定提交
        ===========
            假设你有c1~c5五个提交,你要删除c5这个提交

            git log --oneline
                eb6a3dd (HEAD -> master) c5
                234ad32 c4
                269f234 c3
                bad34cd c2
                245cf8c c1

            git rebase -i <245cf8c>   # git要指定一个起点的,这里以c1为起点,你也可以以其它为起点
                # 此时弹出vim编辑器,内容大致是:开头几行字描述着各个提交该保留还是删除,下面的字讲的是所有可用的操作是什么意思
                pick bad34cd c2
                pick 269f234 c3
                pick 234ad32 c4
                pick eb6a3dd c5

                # balabalabalabalabalabalabalabala
                # balabalabalabalabalabalabalabala
                # balabalabalabalabalabalabalabala

            # 你想删除c5提交,只需把c5前面的pick改成drop即可。更改之后如下:
                pick bad34cd c2
                pick 269f234 c3
                pick 234ad32 c4
                drop eb6a3dd c5

                # balabalabalabalabalabalabalabala
                # balabalabalabalabalabalabalabala
                # balabalabalabalabalabalabalabala

            # vim保存退出即可

            # 注意:
                * 这个命令还有另外一个功效:它可以删除那种没有任何分支指向的提交,方法是你git rebase -i <...> 以后弹出的vim编辑器什么都不改,直接退出
                * 这个命令还有其他功能,比如压缩提交,修改提交信息等,具体请看输入git rebase后的那个弹窗
                * 如果遇到冲突,有三种操作:
                    * 放弃rebase:git rebase --abort
                    * 跳过此次rebase:git rebase --skip
                    * 合并冲突:(编辑冲突文件),然后git add <冲突文件>,然后git rebase --continue
                        * 编辑冲突文件时你会看到你之前的文件变成了类似下面这样:
                          1111
                          aaaa
                        ++<<<<<<< HEAD
                        ++=======
                        + bbbb
                        + dddd
                        ++>>>>>>> 1a91894... V4
                        这上面,只有开头两行是你的。
                        "++<<<<<<< HEAD"意思是这个符号上面的就是你原本的;
                        "++>>>>>>> <提交SHA1> <提交信息>"意思是这行前面直到"++======="是你之前要做的改动;
                        那些以"+"开头的内容得删掉,不然待会git rebase --continue它们会进到追踪库里。改好这个文件后git add <刚刚你改的文件>,然后git git rebase --continue就行了


    分支
    ====
        git checkout -b <新的branch>  # 创建一个新的分支,然后立马切过去
        git branch -d <要删除的分支名字>        # 删除本地分支
        git push origin --delete <要删除的分支名字>     # 删除远程分支。注意:<要删除的分支名字> 是直接写名字的,比如remotes/origin/bugfix这个分支你只要写bugfix就行了
        git branch -f <分支名>       # 强制移动一个分支到一个提交

    合并
    ====
        git merge -e --no-ff   # -e 合并后可以先看一下改了哪些再提交;--no-ff 不使用fast-forward合并策略,虽然git默认使用这种策略,但是
                               # 不推荐,应为这种策略合并后不会创建新的节点

        合并一个分支上的某个文件
        ======================
            有时候你只想合并一个分支上的某个文件到主线上来。
            注意这个命令并不是真的合并,它只是把合并来的内容写到worktree里面!!!

            git checkeout master                # 先切到主线分支
            git checkout -p deve ccc.txt        # "deve ccc.txt"指的是你想合并deve分支上的ccc.txt文件
            
            # 然后就会出现类似下面这种提示,你可以选择要不要合并
            # 我在这里输入了个"?"让它打印出y,n,q,a,d,e的意思,一般就用y和n就够了
                --- b/ccc.txt
                +++ a/ccc.txt
                @@ -1 +1,2 @@
                 333
                +333-333-3333
                Apply this hunk to index and worktree [y,n,q,a,d,e,?]? ?
                y - apply this hunk to index and worktree
                n - do not apply this hunk to index and worktree
                q - quit; do not apply this hunk or any of the remaining ones
                a - apply this hunk and all later hunks in the file
                d - do not apply this hunk or any of the later hunks in the file
                e - manually edit the current hunk
                ? - print help


    删除未被跟踪的文件,delete untracked files
    ==========================================
        # 删除 untracked files
        git clean -f

        # 连 untracked 的目录也一起删掉
        git clean -fd

        # 连 gitignore 的untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o之类的文件用的)
        git clean -xfd

        # 在用上述 git clean 前,强烈建议加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删
        git clean -nxfd
        git clean -nf
        git clean -nfd

    删除已经加入跟踪的文件
    ====================
        # 比如.cproject这个文件已经加入版本跟踪系统了,你想把它从版本跟踪系统中移除
        
        git rm -nr --cached .cproject    # -n:这只是看而已,实际并不移除;-r:递归移除;--cached:从跟踪系统中移除,但仍保留在文件系统中
        
        注意:
            * 只要有“-n”这个参数就不会删除,只是打印出哪些文件会被删除而已,你如果真的十分确定删除了就去掉这个选项
            * 关于“.gitignore”失效的问题,“.gitignore”只能忽略未被跟踪的文件,假如有个文件先被加入跟踪了你才用“.gitignore”忽略它,那么“.gitignore”将失效。解决办法是:用git rm把那个文件取消跟踪,再git add -A


    查看已跟踪的所有文件
    ==================
        git ls-files                      # 列出所有已跟踪文件
        git ls-tree <分支名> --name-only   # 这个好像是列出working space中的所有文件,不建议用这个

    推送
    ===
        git push origin master      # 推送本地的master分支
        git push origin bugfix:bugfix  # 推送本地的bugfix分支(冒号前面)到远程的bugfix分支(冒号后面),如果远程不存在bugfix分支会自动创建;注意冒号两边不能有空格
        git push origin --tags      # 推送本地的所有标签
        git push origin --all       # 推送本地的所有分支
        git push -u origin develop  # 在远程创建一个新分支develop
        git push -u origin master -f # 强制推送,可以解决"hint: Updates were rejected because the tip of your current branch is behind"问题

    其他
    ====
        git config --global core.editor "vim"      # 把默认的编辑器改为vim


正则表达式
==========
    * 正则表达式 [^a]  指的是匹配非a的字符串,[^abc] 匹配的是非a且非b且非c的字符串,val\s*=[^=] 匹配的是val=后面不是=的字符串
    * 正则表达式各种语言都有内嵌,标准各有不同,有时同一个正则表达式在不同的语言环境中会有不同的匹配结果。ps:VIM的正则表达式就跟GNU的grep不同

    正则表达式的一些例子:
    =====================
        (typedef|#define).*uint8_t          ---  typedef或者#define的后面跟上uint8_t
        (?<=\bVERSION\b).*                  ---  匹配#define VERSION 123中的123。注意(?<=xxx)中的xxx必须是定长的,使用GNU的grep要加-P选项,其他的正则表达式不一定支持
        aaa\s*=[^=\r\n]                     ---  匹配aaa后面带"="而不是"=="的字符串,也就是aaa这个变量的赋值语句

SHELL脚本
=========
    查看文件系统类型
    ===============
        df -T
        parted -l
        blkid
        lsblk -f

    查看内核符号表
    ==============
        cat /proc/kallsyms
    

    浮点运算
    ========
        echo | awk '{printf("%.3f\n", 34/0.459834)}'
        echo 'scale=3;34/0.459834' | bc                    # "scale=3;"的意思是小数点后面有3位


    获取ASCII码
        $ xxd
        $ <直接输入你想要转换的字符,可以输入多个>
        $ ctrl+d


    打补丁
    ======
        注意这是unix的diff,不是git diff

        $ diff -nrNp <目录1> <目录2> > diff.patch        # 生成目录2基于目录1的补丁
        $ cd <目录1>                                    # 打补丁前要进入目录1
        $ patch -p1 < ../diff.patch                     # 打补丁,使目录1变得跟目录2一样


    日期转秒
    =======
        date -d "2012-11-12 13:00:00" +"%s"

    秒转日期
    =======
        date -d@1352692800 +"%Y-%m-%d %H:%M:%S"

    同时移动或拷贝多个文件
    ====================
        mv {a.c,b.c,c.c} folder     # 把mv换成cp就是拷贝


    写入二进制数据到文件
    ==================
        echo -e "\x01\x02" > 111.bin

    读取二进制文件
    ===============
            使用xxd命令
            ===========
                xxd -g {m} -c {n} {文件名}     # 其中m是一个单元字节;n是一行几个单元
                例如:
                        xxd -g 1 -c 8 README.txt
                延伸:
                        这个命令可以被VIM调用:
                                %!xxd -g 1 -c 8

            使用od命令
            ==========
                od -t xC -A [doxn] -N [num] -w<列数> <文件名>

                    -t      指定格式
                    x       16进制格式
                    C       字节格式
                    -w      一行显示几列

                    doxn    d:十进制;o:八进制;x:十六进制;n:不显示地址
                    -N[num] 显示num个单位

                    od --traditional -d

                    --traditional       以传统方式接受参数(od可以以好几种方式接受参数)
                    -d                  每两个字节组成一个十进制数
            
                举例:
                    od -t xC -A n -N 80 -w8 ECG_Datas.dat   # 以char为单位(-t xC),不显示地址(-A n),总共显示80个单位(-N 80),一行8个单位(-w8)

    文本替换命令
    ============
        sed -i 's/idata//g' Mifare.c   # 把Mifare.c中的idata全部去掉

        # find命令
        find . ! -name 'port.h'   # 查找不是port.h的文件
        find . -regex ".*dat"     # 查找当前目录下的所有.dat文件,注意使用-regex(正则表达式)以后,匹配的不只是文件名
                                    而是绝对路径
        find . \( ! -name "*.dat" \) -a \( ! -name "*.sh" \)    # 当前目录找不是.dat也不是.sh文件的文件
        find . \( \( -name foo.c \) -o \( -name main.c \) \) -printf "${PWD}/%P size=%sByte\n"  # 找main.c和foo.c并打印出它们的绝对路径和大小

    shell的延时
    ===========
        sleep : 默认为秒。
        sleep 1s 表示延迟一秒
        sleep 1m 表示延迟一分钟
        sleep 1h 表示延迟一小时
        sleep 1d 表示延迟一天

        usleep表示微秒

    date命令
    ========
        tm=`date --date="1 hour ago" +%Y%m%d%H%M`       # 获取当前时间的一小时以前的时间
        echo $tm

        tm=`date --date="20181009 1045 1 hour ago" +%Y%m%d%H%M`     # 获取指定时间一小时以前的时间
        echo $tm

    把字符串写入文件
    ================
        如果字符串只有一行
        ==================
            这不难

                echo "111 222" > a.txt
            或者这样:
                for line in "111 222"
                do
                    echo $line >> a.txt
                done

        如果字符串有多行
        ================
            这个知识点很隐蔽,因为刚学这个的人肯定会以为可以像处理单行字符串那样,不行的。

            用cat:
                text="line 1
                line 2
                line 3"

                cat>$file<                 $text
                EOF

    字符串去重
    =========
        sort -u     # 去除重复只保留一个然后排序
        uniq        # 只去除连续的重复

        引申
        ====
            ls -R | grep -oE '\.[0-9a-zA-Z_\.]+$' | sort -u     # 列出一个目录下的所有文件类型

    列出一个目录下的全部文件
    ======================
        ls -lR | grep -E "^-" | grep -oE ":\w\w .*$" | grep -oE " .*$"

    grep
    ====
        grep -nrE --include=*.{c,h} "mf_"    # -n:打印行号;-r:递归查找;-E:正则表达式;--include=*.{c,h}:去.c和.h文件里面找;"mf_":以mf_开头的东西


    文本编辑命令
    ============
        awk -F" " '{print NF}' read.txt

            -F" "                指定分隔符是空格
            '{print NF}'         打印每一行的列数
            cat yy.txt | awk '{printf "%-30s%-15s\n",$1,$2}' > yy2.txt
                                 把yy.txt中的第一第二列按固定的缩进打印并输出到yy2.txt
                                 # %-30s表示输出字符串,宽度30位,左对齐.%-15s用来指定第二列的,左
                                 # 对齐,宽度15.两个百分号之间可以没有空格.使用\n对每一行的输出加上换行符
                                 # 如果要表示第10列及以上,用$(10),$(11)...

    清空一个文件
    ===========
        cat /dev/null > dat.bin

C语言
===================
    结构体也可以这样定义
    ==================
        struct AAA {
            int a;
            int b;
        } k = {
            a: 123,
            b: 456,
        };


    关于typedef
    ===========
        typedef double  fftw_complex[2];
        fftw_complex a;                             // a是一个由两个double型组成的数组
        fftw_complex *b;                            // b是一个指向数组的指针,改数组由两个double型组成

    用宏定义把数字转成字符串
    ======================
        /*
        * 如果#define SAMPLE_RATE (16000)就不灵了
        */
        #define SAMPLE_RATE 16000
        #define STR1(R)  #R
        #define STR2(R)  STR1(R)

        int main()
        {
            printf(STR1(SAMPLE_RATE) "\n");  /* SAMPLE_RATE */
            printf(STR2(SAMPLE_RATE) "\n");  /* 16000 */
            return 0;
        }

    指向数组的指针
    =============
        int main() {
            const char *p= "12345678";
            char (*q)[2] = (char (*)[2])p;

            printf("%c%c\n", q[0][0], q[0][1]);
            printf("%c%c\n", q[1][0], q[1][1]);

            return 0;
        }


    库函数获取毫秒
    ==============
        #include
        #include

        long long getSystemTime() {
            struct timeb t;
            ftime(&t);
            return 1000 * t.time + t.millitm;
        }

        int main() {
            long long start=getSystemTime();
            sleep(5);
            long long end=getSystemTime();

            printf("time: %lld ms\n", end-start);
            return 0;
        }

    库函数延时函数
    ===================
        sleep()         秒延时
        usleep()        微秒延时

    scanf用法
    ========
        #include
        void main() {
            int num, hour, min, sec;
            printf("num=");
            scanf("%d", &num);
            hour = num / 3600;
            min = num % 3600 / 60;
            sec = num % 60;
            printf("%d 小时 %d 分钟 %d 秒\n", hour, min, sec);
        }

    判断闰年
    =======
        公历闰年判定遵循的规律为: 四年一闰,百年不闰,四百年再闰。
        公历闰年的简单计算方法(符合以下条件之一的年份即为闰年)
            1.能被4整除而不能被100整除。
            2.能被100整除也能被400整除。
        闰年是公历中的名词。闰年分为普通闰年和世纪闰年。
        普通闰年:能被4整除但不能被100整除的年份为普通闰年。(如2004年就是闰年,1999年不是闰年);
        世纪闰年:能被400整除的为世纪闰年。(如2000年是世纪闰年,1900年不是世纪闰年)。

    指向数组的指针
    =============
        char a[30] = "123";
        char (*p)[30] = &a;
        printf("p=%p, a=%p\n", p, a);    // p == a

        知识点
        =====
            * 上面的代码中,如果p++,p的地址一次加30
            * char (*p)[30] = &a; 指向数组的指针就是这样赋值的,&a其实是等于a的

    编程坑点
    =========
        * 取局部变量的地址要注意内存释放的问题。当你使用局部变量的地址引用局部变量时,局部变量可能早就被释放了,所以
          如果非要,记得局部变量加volatile
        * 内存、中断、电压(低功耗)是全局的,任何一个函数都要考虑这三个方面
        * 要有错误处理(日志,打印等)程序,switch和if要做好default和else的检查,空的else最好用/*nothing here*/注明
        * 小心函数的重复运行问题,比如task_create反复执行会创建出很多个一样的任务。另外,中断也是可以打断中断的,也是函数的重复运行问题
        * 全局变量和硬件如果被多任务,中断共享要做好保护措施
        * 系统的初始化也有学问,注意系统的各个软硬件的初始化顺序
        * 小心死锁问题,死锁也能导致死机,发生原因一般是嵌套锁
        * 建议加看门狗,小心看门狗初始化失败时可能仍然生效,如果此时你认为看门狗初始化失败而不喂狗那么软件就会一直复位
        * 测试代码的时候要记录栈使用情况,防止栈溢出
        * 小心C语言的宏定义,比如这个:#define SET_MODE(hd, mode)     led_handles[(hd)].mode = (mode)
          当你这样调用时:SET_MODE(1, sta),展开成了led_handles[(hd)].sta = sta,你本想改变mode成员,结果改到sta成员去了
        * 拷贝大的全局变量时要注意共享问题
        * 大坑:把一个局部变量定义成const仍是局部变量,仍是放在栈里面的!!!
        * 小心float和double的精度问题
        * DMA访问内存不会更新Cache(可能还有别的类似的硬件),所以它可以导致Cache和实际内存不一致,所以DMA读写内存前后要做好Cache的同步措施
        * 小心字符串末尾的零。如果一个字符串末尾没有零,那么strcpy这种函数将是灾难性的
        * &(按位与)的优先级比+,-还低
        * 中断过于频繁地执行会导致程序卡慢甚至完全卡死,所以程序要限制中断的执行频率
        * 用完的指针,句柄,ID等应当置为一个无效值,比如0,避免后面的代码误认为它仍然有效
        * 小心,不要去访问已经被释放掉的内存
        * 有些编译器,函数只声明,不定义,编译是通过的。这可能有问题
        * 小心父进程比子进程先退出

    不安全的C库函数
    ==============
        #ifndef BANNED_H
        #define BANNED_H

        /*
         * This header lists functions that have been banned from our code base,
         * because they're too easy to misuse (and even if used correctly,
         * complicate audits). Including this header turns them into compile-time
         * errors.
         */

        #define BANNED(func) sorry_##func##_is_a_banned_function

        #undef strcpy
        #define strcpy(x,y) BANNED(strcpy)
        #undef strcat
        #define strcat(x,y) BANNED(strcat)
        #undef strncpy
        #define strncpy(x,y,n) BANNED(strncpy)
        #undef strncat
        #define strncat(x,y,n) BANNED(strncat)

        #undef sprintf
        #undef vsprintf
        #ifdef HAVE_VARIADIC_MACROS
        #define sprintf(...) BANNED(sprintf)
        #define vsprintf(...) BANNED(vsprintf)
        #else
        #define sprintf(buf,fmt,arg) BANNED(sprintf)
        #define vsprintf(buf,fmt,arg) BANNED(sprintf)
        #endif

        #endif /* BANNED_H */


PYTHON
======

    编程例子:
    =========
        打印字母A到Z:
            for i in range(ord('A'), ord('Z')):
                print(chr(i))


emWin
=======
    MESSAGEBOX_Create, GUI_MessageBox都是创建消息框函数,区别是前者能重写回调函数(但是回调函数只接
    受WM_PAINT这一个消息),后者不能


STM32HAL库
==========

    怎样初始化一个外设
    ==================
        以串口3为例:
        1. 定义一个串口3的全局句柄USART_HandleTypeDef xxx,后续所有的操作都要用到它
        2. stm32f7xx_hal_msp.c文件的HAL_USART_MspDeInit函数添加串口3的默认初始化函数,一般是关时钟,中断和GPIO默认初始化
        3. stm32f7xx_hal_msp.c文件的HAL_USART_MspInit函数添加串口3外围的初始化函数,包括串口3的时钟和相应的GPIO时钟使能,
           GPIO初始化,串口3的中断初始化
        4. main.c或者其他哪个文件都行,写一个串口3真正的初始化函数,包括波特率,校验位那些

    怎样使用一个中断
    ================
        以串口3为例:
        1. stm32f7xx_it.c文件添加USART3_IRQHandler函数
        2. 重写HAL_UART_XXXtCallback函数
        3. 使用HAL_UART_Receive_IT函数注册中断接收数组
        4. 别忘了在HAL_UART_MspInit中使能串口中断

    其他
    ====
        * HAL库的头文件只需一个#include "stm32f7xx_hal.h"即可
        * 小心DMA,例如你在一个DMA传输函数执行结束后读取内存,这个内存可能仍被DMA控制


Keil MDK
========
    生成bin文件
    ===========
            $K\ARM\ARMCC\bin\fromelf.exe --bin [email protected] !L         # 在工程目录下生成bin文件
            或者
            $K\ARM\ARMCC\bin\fromelf.exe --bin --output=Bin\@L.bin !L     # 在工程目录下的Bin文件夹生成bin文件

    重写函数
    =======
        int $Sub$$main(void)
        {
            __nop();
            $Super$$main();
        }
        
        知识点:
            * 上面的代码中,$Sub$${函数名}是重写一个函数的意思,$Super$${函数名}是原来的函数的意思(你如果想调用以前的函数就必须得加这个开头$Super$$)。不止于main函数,什么函数都可以这样
            * 利用这个功能,你可以在main函数之前做点其他的代码,比如初始化


    怎样重定向printf
    ===============
        1. 添加段代码
        ============
            #include
            int fputc(int ch, FILE *f)
            {
                uint8_t temp[1] = {ch};
                HAL_UART_Transmit(&huart1, temp, 1, 2);
                return(ch);
            }

        2. USE Micro Lib
        ================
            点击魔术棒,找到这项勾上


GCC
====
    生成so文件
    =========
        使用"-fPIC -shared",举例:gcc test.c -fPIC -shared -o libtest.so

        编译时指定so搜索目录
        ==================
            格式:"-Wl,-rpath,/the/search/dir",举例:gcc ... -Wl,-rpath,/root/test/lib -o ...
            指定多个so搜索目录:-Wl,-rpath,/the/search/dir1,-rpath,/the/search/dir2

        小心cygwin的GCC和Linux的GCC在使用so时不一定是相同的。


    生成a文件
    ========
        使用ar命令把o文件生成a文件
        $ gcc -c foo.c -o foo.o
        $ ar r foo.a foo.o [foo2.o foo3.o ...]


    重定向printf
    ============
        可以参考ST的CubeMX生成代码,以后再写笔记


    链接
    ====
        链接并不是只能用LD,GCC也可以,有时甚至链接只能用GCC
        ================================================

        把一个变量放到指定的段内
        =========================
            __attribute__((section("name")))        # name即是你要指定的段,名字随便取,可以带'.'号

            例如:
                int david __attribute__((section(".user.char.1"))) = 0x123;  # 把david这个变量放到".user.char.1"段中

        利用链接脚本导出符号
        ====================
            在C语言里面,你若想导出一个符号,必然要用extern。但是GCC的链接脚本允许你不用那种方法:

                # 链接脚本中定义好".user.char.1"段
                SECTIONS
                {
                    .text :
                    {
                        ...
                        *(.user.char.1)
                        ...
                    }
                }

                # C工程中定义一个位于".user.char.1"段的变量
                int david __attribute__((section(".user.char.1"))) = 0x123;

                # 此时david这个变量就变成全局变量了,你可以在工程的任意一个位置访问它而不需要extern。

                # 实际上,你还可以把链接脚本改成这样:
                SECTIONS
                {
                    .text :
                    {
                        ...
                        user_start = .;
                        *(.user.char.1)
                        *(.user.char.2)
                        *(.user.char.3)
                        *(.user.char.4)
                        user_end = .;
                        ...
                    }
                }
                # 这种情况适用于当你的自定义段很多的时候,你可以通过获取user_start和user_end的地
                # 址(即&user_start, &user_end)来访问到这个“数组”。Linux内核里面有大量应用

GDB
===
    x/32xb buf   # 查看buf内存
    info locals  # 查看局部变量


ubuntu,树莓派,安卓
===================
    安装eclipse
    ===========
        apt-get install eclipse-platform    # eclipse-platform和eclipse不同,前者没有任何语言开发插件;后者带有JAVA开发插件
        apt-get install eclipse-cdt         # 安装C/C++的开发插件

        想用新版eclipse只能手动安装。新一点的ubuntu应该都是可以的,我在ubuntu12上安装的时候,明明JRE是1.8版本,但是eclipse说它是
        1.6版本,跑不起来。


    卸载软件
    =======
        sudo apt-get remove xfce4*  # 把xfce4*改成你要卸载的软件
        sudo apt-get autoremove
        sudo apt-get clean


    查看ubuntu版本
    =============
        lsb_release -a


    开启和关闭ubuntu桌面
    ==================
        sudo service lightdm stop                       # 临时关闭
        sudo service lightdm start                      # 临时开启
        sudo systemctl set-default multi-user.target    # 永久关闭
        sudo systemctl set-default graphical.target     # 永久开启


    安装桌面
    ========
        lxde:   sudo apt-get install lxde
        xfce:    sudo apt-get install xfce4

        有些桌面一旦安装好,重启电脑即可看到,如果没有,使用命令startx启动桌面,使用pkill Xorg关闭
        桌面(先用ctrl+alt+F2切到另一个SSH)


    树莓派技巧
    ==========
        用SD卡开启SSH(仅适用于官方系统):在SD卡根目录新建一个空文件SSH
        用SD卡配置WIFI(仅适用于官方系统):在SD卡根目录新建wpa_supplicant.conf文件,填入
            ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
            network={
              ssid="你的WIFI名"
              psk="WIFI密码"
            }

    查看系统是多少位
    ===============
        getconf LONG_BIT

    su失败的解决办法
    ================
        问题:
            pi@raspberrypi:/ $ su
            密码:
            su:鉴定故障

        解决办法:
            sudo su

    开启SSH服务
    ==========
        ubuntu默认是不使能SSH的,你得自己安装配置(树莓派是安装了SSH的,只是没有使能)

        sudo apt-get install openssh-server
        sudo systemctl restart ssh
        
        其他:
            当你用ssh登录的时候会发现终端变成黑白的,那是因为ubuntu默认就是关闭ssh的彩色显示的。这时你可以编辑~/.bashrc文件,把force_color_prompt赋值yes即可

    挂载U盘
    =======
        $ cat /proc/partitions      # 或者lsblk
            major minor  #blocks  name
            
               8        0   62522712 sda
               8        1     524288 sda1
               8        2   60996608 sda2
               8        3    1000448 sda3
               8       16   31326208 sdb    # 这是U盘
               8       17   31326176 sdb1   # 这是U盘的分区
        $ sudo mount /dev/sdb1 /mnt
        $ sudo umount /mnt # 或者 sudo umount /dev/sdb1

    udev语法简单讲解
    ===============
        用户配置目录位于/etc/udev/rules.d/中,系统预置位于/lib/udev/rules.d/中。文件名必须以.rules结尾。
        rules文件由一系列的规则组成,每个规则又由一系列键值对组成。一个规则就是一行,如果一个规则包含多个键值对,则各个键值对之间要用逗号隔开。
        键值对的逻辑有点像C语言的if else。简单说就是,如果第一个键值对使用了==且成立,那么该规则后面的所有键值对就得以执行,其他诸如!=亦是同理。
        举例:(这是网上截取别人的,并不好用)
            ####################################################################################
            1  KERNEL!="sd[b-z]*", GOTO="exit"
            2  ACTION=="add", PROGRAM!="/sbin/blkid %N", GOTO="exit"
            3
            4  # import some useful filesystem info as variables
            5  IMPORT{program}="/sbin/blkid -o udev -p %N"
            6
            7  # get the label if present, otherwise assign one based on device/partition
            8  ENV{ID_FS_LABEL}!="", ENV{dir_name}="%E{ID_FS_LABEL}"
            9  ENV{ID_FS_LABEL}=="", ENV{dir_name}="flash_drive_%k"
            10
            11 # create the dir in /media and symlink it to /mnt
            12 ACTION=="add", RUN+="/bin/mkdir -p '/media/%E{dir_name}'"
            13
            14 LABEL="exit"
            ####################################################################################
            1. 如果设备名不等于sd[b-z]*,那就跳到exit标签,也就是退出整个rules文件。KERNEL是设备的内核名称,%k是它的简写。
            2. 如果第1行成立,那么如果设备是插入,那就执行"/sbin/blkid %N", GOTO="exit"。PROGRAM!=的意思是需要检查返回值。
            5. 把"/sbin/blkid -o udev -p %N"的执行结果导入program这个变量。%N是设备节点的名称(也就是设备文件的名称)。
            8. 如果ID_FS_LABEL这个环境变量非空,那么把变量ID_FS_LABEL赋给dir_name。%E{*}等价于ENV{*},就是取某个环境变量的意思。
            9. 和第8行很类似,不同的是dir_name被赋值为flash_drive_%k。%k是设备的内核名称,比如/dev/sdb1
            12. 如果是插入动作则执行"/bin/mkdir -p '/media/%E{dir_name}'"。
            14. 一个标签,跟C语言的标签是一个意思。

            # udev的语法太复杂,自己写的话要是写不好可能会导致系统崩溃,所以建议安装XFCE等桌面应用来实现U盘自动挂载


    udev自动挂载U盘和SD卡
    ====================
        这是一个简单的udev挂载U盘的rules文件,使用时注意/etc/udev/rules.d/99-mount-sam.rules中的sd[b-z],它不支持名为sda的设备,因为
        sda一般是系统硬盘,一旦这个rules文件挂载了系统硬盘可能会使整个系统崩溃。所以,在使用前要看系统本身的硬盘是什么,然后修改这个文件,使
        这个文件不会动系统硬盘。

        $ sudo vim /etc/udev/rules.d/99-mount-sam.rules     # 以99开头是因为我想让系统的rules文件先执行
            ACTION=="add", KERNEL=="sd[b-z][0-9]", RUN+="/home/sam/.mount.sh %k"
            ACTION=="remove", SUBSYSTEM=="block", KERNEL=="sd[b-z][0-9]", RUN+="/home/sam/.umount.sh %k"

        $ vim /home/sam/.mount.sh
            #!/bin/sh

            # if the device had been mounted already, no need to mount again
            df | grep -E "^/dev/$1" 1>/dev/null 2>&1
            if [ $? -eq 0 ]; then
                exit
            fi

            mkdir -p /mnt/$1
            mount /dev/$1 /mnt/$1
            sync

        $ vim /home/sam/.umount.sh
            #!/bin/sh
            umount /mnt/$1
            rmdir /mnt/$1
            sync

        $ chmod +x /home/sam/.mount.sh /home/sam/.umount.sh

        $ /etc/init.d/udev restart


    一些Ubuntu软件源
    ===============
        # 阿里云源
        deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse
        deb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse
        deb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse
        deb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse
        ##測試版源
        deb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse
        # 源碼
        deb-src http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse
        deb-src http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse
        deb-src http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse
        deb-src http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse
        ##測試版源
        deb-src http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse


        # 清华大学源
        deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty main restricted universe multiverse
        deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty-security main restricted universe multiverse
        deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty-updates main restricted universe multiverse
        deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty-backports main restricted universe multiverse
        ##測試版源
        deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty-proposed main restricted universe multiverse
        # 源碼
        deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty main restricted universe multiverse
        deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty-security main restricted universe multiverse
        deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty-updates main restricted universe multiverse
        deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty-backports main restricted universe multiverse
        ##測試版源
        deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ trusty-proposed main restricted universe multiverse


    树莓派改软件源
    =============
        sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
        sudo vi /etc/apt/sources.list
            把以前的都删掉,加入deb http://mirrors.ustc.edu.cn/raspbian/raspbian/ stretch main contrib non-free rpi
            保存退出
        sudo cp /etc/apt/sources.list.d/raspi.list /etc/apt/sources.list.d/raspi.list.bak
        sudo vi /etc/apt/sources.list.d/raspi.list
            把以前都删了,加入deb http://mirrors.ustc.edu.cn/archive.raspberrypi.org/debian/ stretch main ui
            保存退出
        sudo su
        apt-get update && apt-get upgrade
        
    ubuntu改软件源
    =============
        1. 改source.list文件
            sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
            sudo vi /etc/apt/sources.list
                # 把上文的“清华大学源”或者“阿里云源”填入/etc/apt/sources.list
                # 把文件中的“trusty”改成你自己的ubuntu代号,这个代号通过“lsb_release -a”获取

            或者:
                去中科大的官网下载一个sources.list文件(可以输入"ubuntu"查找),复制里面的内容到/etc/apt/sources.list就行了。
                这个方法也不推荐,因为中科大的source.list屏蔽了一些东西,会导致一些问题。

            或者:
                sudo sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
                这个方法简单粗暴,但不推荐,会导致一些问题。

        2. 更新
            sudo apt-get update
            sudo apt-get upgrade


    查看有哪些设备连入了本设备的热点
    =============================
        ip neigh

    搭建ftp服务器
    ============
        ubuntu:
            sudo apt-get install vsftpd
            sudo vim /etc/vsftpd.conf
                把#write_enable=YES改成write_enable=YES
                保存退出
            sudo service vsftpd restart     # 当然用sudo systemctl restart vsftpd或者sudo /etc/init.d/vsftpd restart也可以

        windows:
            ftp://  # 例如ftp://192.168.43.254
            账号:pi
            密码:的密码

        结果:
            ubuntu的/home/pi目录共享到了windows上。有关vsftp的所有配置都在/etc/vsftpd.conf文件中
            你可以用man vsftpd或者man vsftpd.conf获取帮助

    搭建samba服务器
    ==============
        ubuntu:
            sudo apt-get update
            sudo apt-get install samba samba-common-bin
            sudo vim /etc/samba/smb.conf
            # 在文件的最底部添加这段代码。注意"="两边可以带空格;好像不可以用tab来当空格
                [pi]                    # "pi"你可以换成自己的名字
                    path = /home/pi     # 共享目录
                    writable = yes
                    readonly = no        # 允许读写;yes是只读
                    public = yes
                保存退出
            sudo chmod -R 7777 /home/pi # 7777中第1个7的意思是uid,gid和粘滞位都是1
            sudo smbpasswd -a pi        # 往samba中添加用户pi,系统会提示输入密码。注意这个用户必须是ubuntu真实存在的用户,你可能需要user add pi
            sudo service smbd restart   # 当然用sudo systemctl restart smbd或者sudo /etc/init.d/smbd restart也可以

        windows:
            文件管理器找到"网络>>RASPBERRYPI>>pi",输入账号pi以及他的密码即可

    搭建nfs服务器
    ============
        Windows挂载nfs非常卡,还不支持中文,不建议使用

        ubuntu:
            sudo apt-get install nfs-kernel-server      # 这步你可能并不需要,因为很多Linux发行版都是自带nfs的。你可以先看看/etc/export是否存在,如果存在则说明安装了
            sudo vim /etc/exports
                末尾添加这个
                /home/pi    *(rw)     # "/home/pi"是你要共享出去的路径;"*"指的是任何网段的主机都可以访问你的nfs;"(rw)"指的是别人可读可写
                保存退出
            chmod 777 /home/pi
            sudo systemctl restart nfs-kernel-server.service    # 当然你用sudo service <服务名> restart或者sudo /etc/init.d/<服务名> restart也可以

        windows:
            控制面板--->程序--->打开或关闭Windows功能。把NFS服务子项全部勾选,"基于UNIX的应用程序子系统"勾选
            cmd窗口输入:mount \\192.168.43.254\home\pi x: 或者 文件管理器--->映射网络驱动器--->填入\\192.168.43.254\home\pi--->确定
            打开文件浏览器,你可以看到ubuntu的nfs挂载上来了,但是是只读的,尽管你的ubuntu已经规定可写了,网上有解决办法,但是我试了不成功
            如果卸载挂载,cmd窗口输入:umount -a -f


    搭建tftp服务器
    =============
        $ sudo apt-get install xinetd tftp tftpd
        $ vi /etc/xinetd.d/tftp
            service tftp
            {
                socket_type     = dgram
                protocol        = udp
                wait            = yes
                user            = root
                server          = /usr/sbin/in.tftpd
                server_args     = -s /home/sam/Work/tftp_share
                disable         = no
                per_source      = 11
                cps             = 100 2
                flags           = IPv4
            }
        $ mkdir /home/sam/Work/tftp_share
        $ chmod 777 /home/sam/Work/tftp_share
        $ sudo /etc/init.d/xinetd restart


eclipse
========
    * 在Windows下,当你用eclipse编译c代码时,工具链(gcc,gdb,make等)得自己准备,网上工具链一大把,推荐使用Qt自带的MinGW,别用CodeBlocks的MinGW,虽然它更全,但是在eclipse下不能正常工作。
    * eclipse在设置工具链时有时不能马上见效,可以删除工程,删除文件系统里的.settings,.project,.cproject文件再重新创建工程
    
    
zsh美化
========
    Linux一般使用bash作为SHELL,但除了bash,还有个zsh,ohmyzsh就是一个美化zsh的软件

    安装
    ====
        1.
            sudo apt-get install zsh
            sudo chsh -s $(which zsh)

        2.
            有了zsh,你还得安装它的配置文件,这样终端才会五彩斑斓起来。这个配置文件最流行的就是"Oh My Zsh":
            sh -c "$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
            有时,这个命令并不管用,你可以试试这个:
            git clone https://github.com/ohmyzsh/ohmyzsh.git
            ./ohmyzsh/tools/install.sh      # 小心这步执行到一半会让你输入密码(如果你选择了把zsh作为默认shell的话),如果你输错密码就得执行uninstall.sh重来

bash美化
=======
    类似于zsh,bash也有美化软件。如果你不适应zsh的语法,又不喜欢bash默认的配色,可以试试使用bash美化软件,oh-my-bash就是其中之一。
    地址:https://github.com/ohmybash/oh-my-bash.git。使用跟ohmyzsh很类似。
    使用官网推荐的curl和wget经常无效,所以推荐手动安装:
    git clone git://github.com/ohmybash/oh-my-bash.git ~/.oh-my-bash
    cp ~/.bashrc ~/.bashrc.orig
    cp ~/.oh-my-bash/templates/bashrc.osh-template ~/.bashrc
    # 然后你根据自己的需要编辑~/.bashrc
    source ~/.bashrc
    # 我发现这个软件有些主题显示的时间是12小时,很不习惯,可以这样改:
    #   vim ~/.oh-my-bash/themes/candy/candy.theme.sh
    #       THEME_CLOCK_FORMAT="%H:%M:%S"   # 把原来的THEME_CLOCK_FORMAT赋值语句删除,添加这句


设置网络
=======
    nmtui                   字符GUI工具,进去以后你自然懂得怎么用。推荐
    nm-connection-editor    图形GUI工具,进去以后你自然懂得怎么用。在没有桌面环境和x-windows的系统中没法用
    nmcli                   纯字符工具,操作超级复杂,不记了
    ifconfig                纯字符工具,操作比较复杂,不记了


查找一个软件在哪个包
==================
    apt-cache search xxx       # 只是列出一些跟xxx相关的软件,具体要哪个还得你自己定


设置时区
=======
    sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime


终端分屏工具terminator
======================
    $ sudo apt-get install terminator
    $ terminator

    ctrl+shift+o            水平分割
    ctrl+shift+e            垂直分割
    ctrl+shift+上下左右     调整窗口大小
    ctrl+tab                切换窗口
    ctrl+shift+w            关闭窗口


MobaXterm
==========
    解决有时X11启动不来的问题:
        1. 使用Ubuntu默认用户登录
        2. cp /home/<能用X11的用户>/.Xauthority /home/<不能用X11的用户>/.Xauthority

 

 

你可能感兴趣的:(linux,STM32)