OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
楼主: it_do_just

初学者即将毕业实习,希望利用最后两个月的学习顺利找到工作,占个地方做些笔记O(∩_∩)O哈哈~

    [复制链接]

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-5 09:05:11 | 显示全部楼层
回复【148楼】FantaSy_:
---------------------------------
回复【149楼】汪凯露露:
---------------------------------
谢谢支持!
我的博客:http://blog.csdn.net/itdo_just
正点原子逻辑分析仪DL16劲爆上市
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-7 12:52:04 | 显示全部楼层
本帖最后由 1208 于 2019-11-18 12:16 编辑

<p class="MsoNormal">
        <span style="font-size:16px;color:#E53333;"><strong>实战:内存管理<br>
<span style="color:#000000;"><br>

<br>
<br>
本章没有硬件部分,所以以讲解代码为主,下面是我代码的备注<br>
</span></strong></span>
</p>
<div style="background-color:#E8E8E8;">
        <strong>
[mw_shl_code=c,true]#include "malloc.h"

__align(4) u8 membase[MEM_MAX_SIZE];  //SRAM内存池,数组首地址4字节对齐
u16 memmapbase[MEM_ALLOC_TABLE_SIZE]; //SRAM内存池MAP

//内存管理参数
const u32 memtblsize = MEM_ALLOC_TABLE_SIZE; //内存表大小
const u32 memblksize = MEM_BLOCK_SIZE;             //内存分块大小
const u32 memsize = MEM_MAX_SIZE;            //内存总大小

struct _m_malloc_dev malloc_dev =
{
        mem_init,    //初始化函数
        mem_perused, //内存使用率
        membase,     //内存池
        memmapbase, //内存管理状态表
        0,           //内存管理未就绪
};

//内存管理初始化  
void mem_init(void)
{
        mymemset(malloc_dev.membase, 0, memsize);//内存池所有数据清零
        mymemset(malloc_dev.memmap,  0, memtblsize*2); //因为一次只操作一个字节所以乘以2,memmapbase为2字节大小
        malloc_dev.memrdy = 1; //内存管理初始化OK  
}


//复制内存
//*des:目的地址
//*src:源地址
//n:需要复制的内存长度(字节为单位)
void mymemcpy(void *dest,void *src, u32 cout) //赋值内存
{
        u8 *xdest=dest; //这里之所以强制转换为u8是因为每次只操作一字节
        u8 *xsrc = src; //强制转换后能确保每个字节都被设置了
        while(cout--)
                *xdest++ = *xsrc++;
}

//设置内存
//*s:内存首地址
//c :要设置的值
//count:需要设置的内存大小(字节为单位)
void mymemset(void *s, u8 ch, u32 cout) //内存设置
{
        u8 *xs = s;  //这里之所以强制转换为u8是因为每次只操作一字节
       
        while(cout--)
                *xs++ = ch;//为什么不直接用*s而要赋值给*xs呢?
                     //因为直接用*s不知道具体下个地址是几个字节后(void *s)
                                                         //强制转换后能确保每个字节都被设置了
}

//获取内存使用率
//返回值:使用率(0~100)
u8 mem_perused(void)
{
        u32 cout;
        u32 used;
       
        for(cout=0; cout&lt;memtblsize; cout++)//对所有内存块的遍历
        {       
                if(malloc_dev.memmap[cout])  //如果某个块不为0说明这个块被占用
                        used++;                    //占用率增加
        }
        return (used*100)/(memtblsize);//返回占用率百分比
}

//内存分配(内部调用)
//memx:所属内存块
//size:要分配的内存大小(字节)
//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址
u32 mem_malloc(u32 size)
{
  signed long offset=0;
        u16 nmemb;   //需要的内存块数  
        u16 used=0;  //连续空内存块数
        u32 i;

        if(!malloc_dev.memrdy)
                 malloc_dev.init();   //未初始化,先执行初始化
        if(size==0)
                return 0XFFFFFFFF;    //不需要分配
        nmemb = size/memblksize;    //获取需要分配的连续内存块数
        if(size%memblksize)nmemb++; //如果需要分配内容剩下的部分不到一个块,则算一个块(即有2.5个块算3块内存)
        for(offset=memtblsize-1; offset&gt;=0; offset--) //搜索整个内存控制区
        {
                 if(!malloc_dev.memmap[offset])//连续空内存块数增加
                   used++;
                 else
                         used=0;                      //连续内存块清零
                 if(used == nmemb)  //找到了连续nmemb个空内存块
                 {
                         for(i=0; i&lt;nmemb ;i++)    //标注内存块非空
                         {
                                 malloc_dev.memmap[offset + i] = nmemb;//标注有多少个连续的内存块
       }
                         return (offset*memblksize);                        //返回偏移地址  
                 }
        }
        return 0XFFFFFFFF;   //未找到符合分配条件的内存块  
}

//释放内存(内部调用)
//offset:内存地址偏移
//返回值:0,释放成功;1,释放失败;  
//需要注意的是这个形参的地址不能随便放,必须是申请了内存的首地址
u8 mem_free(u32 offset)
{
        int i;
        if(!malloc_dev.memrdy)
        {
                malloc_dev.init();   //未初始化,先执行初始化
                return 1;   //未初始化  
        }
  if(offset&lt;memsize)    //偏移在内存池内.
        {
                int index = offset/memblksize;      //偏移所在内存块号码  
                int nmemb = malloc_dev.memmap[index];  //内存块数量      
                for(i=0; i&lt;nmemb; i++)                //内存块清零
                {
                         malloc_dev.memmap[index+i] = 0;
                }
                return 0;
        }
        else return 2;    //偏移超区了.  
}

//释放内存(外部调用)
//ptr:内存首地址
void myfree(void *ptr)
{
        u32 offset;
  if(ptr==NULL)return;  //地址为0. NULL=0
        offset=(u32)ptr - (u32)malloc_dev.membase;  //内存指定地址-堆首地址
//注意减去的这个地址是堆首地址,而堆的地址是向上增长的,所以这个ptr也不能小于堆首地址
        mem_free(offset);  //释放内存
       
}

//分配内存(外部调用)
//size:内存大小(字节)
//返回值:分配到的内存首地址.
void *mymalloc(u32 size)
{
        u32 offset;
  offset = mem_malloc(size);  //内存偏移地址
        if(offset == 0XFFFFFFFF)return NULL;
        else return (void *)((u32)malloc_dev.membase+offset);//堆首地址+内存偏移量
        //至于这里为什么返回(void *)这个指针,应该是为了符合C库的规范
}  


//重新分配内存(外部调用)
//*ptr:旧内存首地址
//size:要分配的内存大小(字节)
//返回值:新分配到的内存首地址.
void *myrealloc(void *ptr,u32 size)
{
        u32 offset;
        offset = mem_malloc(size);  //内存偏移地址
        if(offset == 0XFFFFFFFF)return NULL;
        else
        {
                mymemcpy((void *)((u32)malloc_dev.membase+offset), ptr, size); //拷贝旧内存内容到新内存   
                myfree(ptr);       //释放旧内存
                return (void*)((u32)malloc_dev.membase+offset);                          //返回新内存首地址
        }
}        [/mw_shl_code]
</strong>
</div>
<strong>本章自己根据内存管理实现了上一章FLASH的内存分配,上一章需要2K的空间给FLASH以便存放读出来的数据,需要定义一个2K的内存而且只给FLASH分配使用显然很浪费,学了这章后自己用malloc分配了一个2K的内存给FLASH,用完以后就释放了,后面用原子哥代码提供的内存使用率函数查看也发现使用malloc分配的FLASH函数用完就释放内存了所以占用率为0,添加几行代码到"STMFLASH_Write"函数就行了,如下<br>
<div style="background-color:#E8E8E8;">
[mw_shl_code=c,true]#define STM_SECTOR_SIZE        2048
u16 *STMFLASH_BUF = 0;
STMFLASH_BUF = mymalloc(STM_SECTOR_SIZE);
myfree(STMFLASH_BUF);//最后释放内存[/mw_shl_code]
</div>
最后的主函数修改如下<br>
<div style="background-color:#E8E8E8;">
[mw_shl_code=c,true]LCD_printf(0,190,"SRAM USED:%d%%",mem_perused());//显示内存使用率
key = KEY_Scan(0);//不支持连按
switch(key)
{
        case KEY0_PRES:       //K0按下
                STMFLASH_Write(FLASH_SAVE_ADDR,(u16*)TEXT_Buffer,SIZE);
                LCD_printf(0,80,"WRITE_OK");
        break;
                       
        case KEY1_PRES:       //K1按下
                STMFLASH_Read(FLASH_SAVE_ADDR,(u16*)datatemp,SIZE);
                LCD_printf(0,100,"%s",datatemp);
        break;
                       
        case WKUP_PRES:       //WK按下
                p = mymalloc(2000);       
                if(p != NULL)
                {       
                        sprintf((char*)p,"SRAM + FLASH TEST!");//向p写入一些内容
                        LCD_printf(0,150,"%s",p);
                }
        break;                       
        default:break;
}&nbsp;&nbsp;&nbsp;&nbsp;[/mw_shl_code]
</div>
按下K0是往FLASH写数据,同时可以观察到内存使用率还是为0,按下K1是读取FLASH内指定地址的数据,WK是开辟内存,可以发现内存使用率上升,如下图片效果<br>

<br>
<br>
http://www.openedv.com/posts/list/39809.htm &nbsp;这个酷帖的7楼有对这段代码的完整解析</strong>
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-12-7 23:11:58 | 显示全部楼层
加油.....
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-9 08:50:41 | 显示全部楼层
实战:SD卡



下面是我对程序部分的详解

初始化与 SD 卡连接的硬件条件( MCU 的 SPI 配置, IO 口配置);

这章使用的是SPI模式,初始化过程

1.初始化SPIIO口并设置为低速模式

2.上电延时( >74 个 CLK),发送数据前首先发送最少74个脉冲,如下面的资料所示

for(i=0;i<10;i++)SD_SPI_ReadWriteByte(0XFF);//原子哥代码发送了80个脉冲



标准容量的数据格式,从以下手册的资料可以看出发送和接收都是从高位到低位,

所以原子哥代码中是设置的先送高位再送低位"SPI1->CR1|=0<<7; //MSBfirst"





原子哥代码里面都是自己计算CRC校验值,CRC校验值公式(如下图所示),看不懂的可以看下下面这个网址的解析

CRC计算解析网址:http://www.baiheee.com/Documents/090107/090107122214.htm

如果懒得看过程可以看下论坛网友有提供一个计算校验值的软件,在下面的附件中


//SD卡发送一个命令的函数

//输入: u8 cmd   命令

//      u32 arg  命令参数

//      u8 crc   crc校验值           

//返回值:SD卡返回的响应  

u8  SD_SendCmd(u8 cmd, u32 arg, u8 crc) //下面的指令都是本函数里面的,分开讲解

SD_DisSelect();//取消上次片选,提供额外的8个时钟



if(SD_Select())return 0XFF;//拉低片选,发送0xFF检测回应,返回0检测成功,接着用SPI读写一个

字节分别写入指令,指令的格式如下,由6个字节组成且最高两位固定为”01”,原子哥代码里面首

先发了命令,然后是4字节参数,图下资料可以看出。最后是CRC校验值,可以查看原子哥代码

里面的这个函数” SD_SendCmd(u8 cmd, u32 arg, u8 crc)”


上面的发送指令过程完成后就可以开始读取回应了,如果回应的最高位(bit7)0的话表示响应成功,代码如下

do

{

r1=SD_SPI_ReadWriteByte(0xFF);

}while((r1&0X80) && Retry--);         //r1开始位始终为0

原子哥提供的资料个很清晰的表格显示对应指令返回的数据格式(R1/R3/R7),指令CM0对应返回R1,如下表


并且给出了R1的数据格式,当然这些资料在SD卡2.0协议也可以查找的到,如下图



然后复位SD卡( 通过指令CMD0),进入 IDLE 状态;

do{

     r1=SD_SendCmd(CMD0,0,0x95);//复位SD,进入IDLE(闲置)状态校验值可以通过CRC计算得出

}while((r1!=0X01) && retry--);//判断是否在空闲状态


如果上面判断为空闲状态就可以开始发送CMD8,检查是否支持 2.0 协议;

根据不同协议检查 SD 卡(命令包括: CMD55、 CMD41、 CMD58 和 CMD1 等),代码:

if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0  检测接口状态,返回R1值为1表示仍处于空闲状态

下面的图对这个函数的"形参"进行了详细的描述,如下



发送完主机的信息后便可以读出SD的回应信息了,根据返回的信息进行判断了,如下代码和对应资料

for(i=0;i<4;i++)   //连续读出4个字节,从高位读起所以是第二个字节和第三个字节,R1已经由上面那段读取了

buf=SD_SPI_ReadWriteByte(0XFF);      //Get trailing return value of R7 resp

if(buf[2]==0X01&&buf[3]==0XAA)        //卡是否支持2.7~3.6V,根据手册buf[2]为电压值



检测完电压支持后发送CMD55,告诉 SD 卡,下一个是特定应用命令,之后发送这个特定应用指令CMD41

发送给主机容量支持信息和激活卡初始化过程,如下代码和资料

do

{

         SD_SendCmd(CMD55,0,0X01);        //发送CMD55

r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41,发送给主机容量支持信息和激活卡初始化过程,HCS位设置为1

}while(r1&&retry--);  //直到退出闲置状态




发送完CMD41并且检测到处于运行状态后就可以连续读出4个字节的数据(得出OCR值),如下代码和资料
[mw_shl_code=c,true]if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始 { for(i=0;i<4;i++)                     buf=SD_SPI_ReadWriteByte(0XFF);//得到OCR值 if(buf[0]&0x40)                     SD_Type=SD_TYPE_V2HC; //检查HCS,即判断容量为高容量还是标准容量 else                     SD_Type=SD_TYPE_V2; } [/mw_shl_code]



最后取消片选(后期读数据会拉低片选)然后选择为高速模式完成整个初始化就好了

SD_DisSelect();//取消片选

SD_SPI_SpeedHigh();//高速

if(SD_Type)return 0;

else if(r1)return r1;            

return 0xaa;//其他错误

 

初始化完以后便可以开始读取数据了,由于一次需要读的数据比较多,为了内存的可重用性,原子哥用了内存管理

来申请堆空间进行数据读写,程序如下
[mw_shl_code=c,true]void SD_Read_Sectorx(u32 sec) { u8 *buf; u16 i; buf=mymalloc(512); //申请内存 if(SD_ReadDisk(buf,sec,1)==0) //读取0扇区的内容 { LCD_ShowString(60,190,200,16,16,"USART1 Sending Data..."); printf("SECTOR 0 DATA:\r\n"); for(i=0;i<512;i++)                     printf("%x ",buf);//打印sec扇区数据 printf("\r\nDATA ENDED\r\n"); LCD_ShowString(60,190,200,16,16,"USART1 Send Data Over!"); } myfree(buf);//释放内存 } u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt) { u8 r1; if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//转换为字节地址,高容量为固定的512所以不用设置,但是标准容量需要设置为地址512倍数开始 if(cnt==1) { r1=SD_SendCmd(CMD17,sector,0X01);//读命令 if(r1==0)//指令发送成功 { r1=SD_RecvData(buf,512);//接收512个字节 } }else { r1=SD_SendCmd(CMD18,sector,0X01);//连续读命令 do { r1=SD_RecvData(buf,512);//接收512个字节 buf+=512; }while(--cnt && r1==0); SD_SendCmd(CMD12,0,0X01); //发送停止命令 } SD_DisSelect();//取消片选 return r1;// } [/mw_shl_code]
CMD17为读取一个块的数据,CMD18为连读




下面附件中的一个软件为本论坛网友提供的CRC校验码的取值,第一个网址链接评论中有本软件的使用方法,第二附件是原子哥转载的讲解SD卡的驱动过程,需要的可以看一下
http://www.openedv.com/posts/list/26941.htm

crc7.exe

8.5 MB, 下载次数: 3171

SD卡驱动.pdf

605.54 KB, 下载次数: 211

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-12 10:03:51 | 显示全部楼层
实战:FATFS

这章的内容比较多,且网上的总结和资料都很多,比起自己做的笔记要更加详细和清晰,自己将原子哥移植FATFS的代码抄了一遍下来后发现其实移植FATFS的部分不是很难,只需要将自己之前写好的SD卡驱动、Flash驱动或其他外设存储设备的驱动直接搬到diskio.c已经搭好的架构里面就行了,修改完之后在ffconf.h里面有很多的宏定义,主要是定义使用者需要用到的功能,每个宏都有详细的英文注释,根据自己需要修改就好了。

其中,与平台无关的是:
ffconf.h FATFS 模块配置文件
ff.h FATFS 和应用模块公用的包含文件
ff.c FATFS 模块
diskio.h FATFS 和 disk I/O 模块公用的包含文件
interger.h 数据类型定义
option 可选的外部功能(比如支持中文等)

与平台相关的代码(需要用户提供)是:
diskio.c FATFS 和 disk I/O 模块接口层文件

自己上官网看了下发现Fatfs已经更新到了R0.11a,自己下载后发现跟R0.10a的差别不是特别大,直接根据原子哥的移植方法可以直接使用
移植完FATFS的框架以后,我发现难写一点的反而是扩展部分的代码,下面放一些写的比较好的资料,方便以后用到可以查看

FatFs官网: http://elm-chan.org/fsw/ff/00index_e.html

FatFs源码剖析:http://www.openedv.com/posts/list/27427.htm


FatFs通用FAT文件系统模块中文手册.pdf

553.74 KB, 下载次数: 681

fat文件系统原理.pdf

970.39 KB, 下载次数: 796

ff11a.zip

1.57 MB, 下载次数: 1501

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

0

主题

5

帖子

0

精华

初级会员

Rank: 2

积分
133
金钱
133
注册时间
2015-10-19
在线时间
14 小时
发表于 2015-12-15 15:03:12 | 显示全部楼层
佩服楼主,
基础知识普及到位。
像楼主学习,与楼主共勉。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-15 16:57:38 | 显示全部楼层
回复【155楼】CAIQ:
---------------------------------
谢谢支持
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-15 17:29:33 | 显示全部楼层

实战:汉字显示实验

 

简介:

常用的汉字内码系统有GB2312,GB13000,GBK,BIG5(繁体)等几种,其中GB2312支持的汉字仅有几千个,很多时候不够用,而GBK内码不仅完全兼容GB2312,还支持了繁体字,总汉字数有2万多个汉字在液晶上的显示原理与前面显示字符的是一样的。汉字在液晶上的显示其实就是一些点的显示与不显示,这就相当于我们的笔一样,有笔经过的地方就画出来,没经过的地方就不画。所以要显示汉字,我们首先要知道汉字的点阵数据,这些数据可以由专门的软件来生成。知道显示了一个汉字,就可以推及整个汉字库了。汉字在各种文件里面的存储不是以点阵数据的形式存储的(否则那占用的空间就太大了),而是以内码的形式存储的,就是GB2312/GBK/BIG5 等这几种的一种,每个汉字对应着一个内码,在知道了内码之后再去字库里面查找这个汉字的点阵数据,然后在液晶上显示出来。这个过程我们是看不到,但是计算机是要去执行的。单片机要显示汉字也与此类似:汉字内码(GBK/GB2312)->查找点阵库->解析->显示。

 

Unicode(统一码、万国码、单一码):是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。长文件名是以UNICODE状态存储的,要转换为GBK码,才方便显示。原子哥代码里面有个mycc936.c这个程序,就是用于UNCIODE和GBK转换使用的。如果不用FAT可以不加这个.c文件

 

GBK编码:是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。

 

bin文件:二进制文件,其用途依系统或应用而定 。一种文件格式binary的缩写。一个后缀名为".bin"的文件,只是表明它是binary格式。比如虚拟光驱文件常用".bin"作为后缀,但并不意味着所有bin文件都是虚拟光驱文件。

 

每个GBK码由2个字节组成,第一个字节为0X81~0XFE,第二个字节分为两部分,一是0X40~0X7E,二是0X80~0XFE。把第一个字节代表的意义称为区,那么 GBK 里面总共有126个区(0XFE-0X81+1),每个区内有 190 个汉字( 0XFE-0X80+0X7E-0X40+2),总共就有 126*190=23940 个汉字。点阵库只要按照这个编码规则从 0X8140 开始,逐一建立,每个区的点阵大小为每个汉字所用的字节数*190。这样,就可以得到在这个字库里面定位汉字的方法:

当 GBKL<0X7F 时: Hp=((GBKH-0x81)*190+GBKL-0X40)*csize;
当 GBKL>0X80 时: Hp=((GBKH-0x81)*190+GBKL-0X41)*csize;

其中 GBKH、GBKL分别代表GBK的第一个字节和第二个字节(也就是高位和低位), Hp为对应汉字点阵数据在字库里面的起始地址(假设是从 0 开始存放),csize代表一个汉字点阵所占的字节数。

 

下面附上我对原子哥代码的解析

一开始显示检测有无初始化过
[mw_shl_code=c,true]//初始化字体 //返回值:0,字库完好. // 其他,字库丢失 u8 font_init(void) { SPI_Flash_Init();//SPI的IO口初始化 //FONTINFOADDR:MiniSTM32是从4.8M+100K地址开始的,最开始的33个字节给ftinfo用 //#define FONTINFOADDR (4916+100)*1024 SPI_Flash_Read((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));//读出ftinfo结构体数据 if(ftinfo.fontok!=0XAA)return 1;//字库错误.没有进行初始化过 return 0; //已经初始化过一次了 } [/mw_shl_code]

没有初始化过执行初始化代码,由于原子哥代码的字库是从SD卡里面读取出来的,所以需要先初始化SD卡

SD_Initialize();
之后开始更新字体,有GBK24/GBK16/GBK12 三种字体
[mw_shl_code=c,true]//更新字体文件,UNIGBK,GBK12,GBK16,GBK24一起更新 //x,y:提示信息的显示地址 //size:字体大小 //提示信息字体大小 //返回值:0,更新成功; // 其他,错误代码. u8 update_font(u16 x,u16 y,u8 size) { u8 *gbk24_path=(u8*)GBK24_PATH; //const u8 *GBK24_PATH="0:/SYSTEM/FONT/GBK24.FON"; GBK24的存放位置 u8 *gbk16_path=(u8*)GBK16_PATH; //const u8 *GBK16_PATH="0:/SYSTEM/FONT/GBK16.FON";     GBK16的存放位置 u8 *gbk12_path=(u8*)GBK12_PATH; //const u8 *GBK12_PATH="0:/SYSTEM/FONT/GBK12.FON"; GBK12的存放位置 u8 *unigbk_path=(u8*)UNIGBK_PATH;//const u8 *UNIGBK_PATH="0:/SYSTEM/FONT/UNIGBK.BIN"; UNIGBK.BIN的存放位置 u8 res; res=0XFF; ftinfo.fontok=0XFF; //字库存在标志,0XAA,字库正常;其他,字库不存在 SPI_Flash_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));//清除之前字库成功的标志.防止更新到一半重启,导致的字库部分数据丢失. SPI_Flash_Read((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); //重新读出ftinfo结构体数据 LCD_ShowString(x,y,240,320,size,"Updating UNIGBK.BIN"); res=updata_fontx(x+20*size/2,y,size,unigbk_path,0); //更新UNIGBK.BIN if(res)return 1;//res不为0即更新不成功 LCD_ShowString(x,y,240,320,size,"Updating GBK12.BIN "); res=updata_fontx(x+20*size/2,y,size,gbk12_path,1); //更新GBK12.FON if(res)return 2; LCD_ShowString(x,y,240,320,size,"Updating GBK16.BIN "); res=updata_fontx(x+20*size/2,y,size,gbk16_path,2); //更新GBK16.FON if(res)return 3; LCD_ShowString(x,y,240,320,size,"Updating GBK24.BIN "); res=updata_fontx(x+20*size/2,y,size,gbk24_path,3); //更新GBK24.FON if(res)return 4; //全部更新好了 ftinfo.fontok=0XAA; //写入0XAA告诉单片机字库更新完了并且后面再重启不需要更新自哭了 SPI_Flash_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); //保存字库信息 return 0;//无错误. } [/mw_shl_code]
程序中最主要的是updata_fontx这个函数,里面首先开辟一些内存用于存储字库文件的信息(例如文件地址,文件大小),这样可以通过地址告诉单片机从SD卡的哪个位置开始读取数据到FLASH,而文件大小可以用于给里面这个函数“fupd_prog(x,y,size,fftemp->fsize,offx);”用于显示当前字体的更新进度,程序中用动态内存分配开辟4096个字节的内存用于通过SPI从SD里面读出4096个字节到FLASH里面,每次读4096个字节直至读取整个字库读取完成,读取完以后释放内存,代码如下
[mw_shl_code=c,true]//更新某一个 //x,y:坐标 //size:字体大小 //fxpath:路径 //fx:更新的内容 0,ungbk;1,gbk12;2,gbk16;3,gbk24; //返回值:0,成功;其他,失败. u8 updata_fontx(u16 x,u16 y,u8 size,u8 *fxpath,u8 fx) { u32 flashaddr=0; FIL * fftemp; u8 *tempbuf; u8 res; u16 bread; u32 offx=0; u8 rval=0; fftemp=(FIL*)mymalloc(sizeof(FIL)); //分配内存(大小为输入进来的文件路径信息容量) if(fftemp==NULL)rval=1; tempbuf=mymalloc(4096); //分配4096个字节空间,. if(tempbuf==NULL)rval=1; res=f_open(fftemp,(const TCHAR*)fxpath,FA_READ);//通过文件路径读出文件信息到fftemp if(res)rval=2;//打开文件失败 if(rval==0) { switch(fx) { case 0: //更新UNIGBK.BIN ftinfo.ugbkaddr=FONTINFOADDR+sizeof(ftinfo); //信息头之后,紧跟UNIGBK转换码表 ftinfo.ugbksize=fftemp->fsize; //UNIGBK大小 flashaddr=ftinfo.ugbkaddr; break; case 1: ftinfo.f12addr=ftinfo.ugbkaddr+ftinfo.ugbksize; //UNIGBK之后,紧跟GBK12字库 ftinfo.gbk12size=fftemp->fsize; //GBK12字库大小 flashaddr=ftinfo.f12addr; //GBK12的起始地址 break; case 2: ftinfo.f16addr=ftinfo.f12addr+ftinfo.gbk12size; //GBK12之后,紧跟GBK16字库 ftinfo.gbk16size=fftemp->fsize; //GBK16字库大小 flashaddr=ftinfo.f16addr; //GBK16的起始地址 break; case 3: ftinfo.f24addr=ftinfo.f16addr+ftinfo.gbk16size; //GBK16之后,紧跟GBK24字库 ftinfo.gkb24size=fftemp->fsize; //GBK24字库大小 flashaddr=ftinfo.f24addr; //GBK24的起始地址 break; } while(res==FR_OK)//死循环执行 { res=f_read(fftemp,tempbuf,4096,(UINT *)&bread); //读取数据(从fftemp读取4096个数据到tempbuf) if(res!=FR_OK)break; //执行错误 SPI_Flash_Write(tempbuf,offx+flashaddr,4096); //从0开始写入4096个数据 offx+=bread;//下一个4096 fupd_prog(x,y,size,fftemp->fsize,offx); //进度显示 if(bread!=4096)break; //读完了. } f_close(fftemp); } myfree(fftemp); //释放内存 myfree(tempbuf); //释放内存 return res; } [/mw_shl_code]
初始化完以后外面FLASH里面的指定位置就有了字库,便可以通过类似show_char的方式去读取汉字的点阵集,下面是实现显示汉字的函数解析
[mw_shl_code=c,true]//显示一个指定大小的汉字 //x,y :汉字的坐标 //font:汉字GBK码 //size:字体大小 //mode:0,正常显示,1,叠加显示 void Show_Font(u16 x,u16 y,u8 *font,u8 size,u8 mode) { u8 temp,t,t1; u16 y0=y; u8 dzk[72]; //存放点阵数据 u8 csize=(size/8+((size%8)?1:0))*(size);//得到字体一个字符对应点阵集所占的字节数 if(size!=12&&size!=16&&size!=24)return; //不支持的size Get_HzMat(font,dzk,size); //得到相应大小的点阵数据 for(t=0;t<csize;t++) //字节数遍历 { temp=dzk[t]; //得到点阵数据 for(t1=0;t1<8;t1++) //一个一个字节画点 { if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR); //画点 else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);//跟背景同色,相当于不画点 temp<<=1;//下一个bit y++; //一个字节8行 if((y-y0)==size)//从上到下,从左到右的扫描方式,行数加到字的大小了 { y=y0; //回到字第一行,开始下一列扫 x++; //列数+1 break;//提前结束循环 } } } } [/mw_shl_code]


本章总结:这章由于自己有SD卡所以可以预先把字库放入SD卡后通过单片机使用FATFS文件管理将字库从SD卡读到FLASH里面,每个字库都有相应的地址,直接根据GBK内码对应上地址增量就可以读出想要的字了。但是如果没有SD卡的情况下,可能就需要通过其他途径将字库写入FLASH,当然也可以通过FAT的方式去管理FLASH写入字库。但是那样貌似有点大材小用而且增加了程序的难度,以下有几种方式可以实现通过串口将字库写入FLASH的:

1.通过串口的文件发送一个一个字节写入单片机里面再存入FALSH,每发送一个字节往FLASH写入一个字节,这种方式虽然简单,但是耗时比较长,每次收到数据就中断一次,并且往FLASH写入也需要一定时间

 

2.建立两个buff缓冲区,一个区假设4096个字节,每次串口发送完4096个字节存入缓冲区,然后将这个缓冲区一次写入FLASH,在写入的期间串口收到的数据放入第二个缓冲区,这样交替存放便可以忽略写入FLASH的时间了,但是还是存在收到一个字节中断一次的问题,如果不在程序运行期间去更新字库的话这样完成没问题,如果需要在程序运行期间去更新字库的话这样的中断对程序可能有影响,因此有了下面第三种方法,也是受论坛网友启发

 

3.建立两个buff缓冲区,采用DMA的双缓冲循环方式,但是貌似F1没有这种模式,F4是有的,但是个人感觉应该也可以用F1双缓存方式来写入数据,自己没有实际运行过,F1由于不支持双缓冲循环方式,而且有个限制,如下:


也就是不能在写完4096个字节后马上更换缓冲区,需要停止通道后更换缓冲区再开启通道,这个执行时间应该能在下一个bit来之前执行完,由于没有亲测过所以没法验证,等有空再测试下,但是F4是不存在这样的问题的,它可以支持双缓冲循环方式

4.再就是原子哥的方式,提前放在SD卡里面直接从SD卡更新字库到FLASH里面

 

5.可以使用FATFS对FLASH进行管理,直接将需要的字库放入FLASH里面

 

下面是几个有关往FLASH写入字库的网页

http://www.openedv.com/posts/list/27005.htm

http://www.openedv.com/posts/list/64048.htm

http://www.openedv.com/posts/list/40143.htm

http://www.amobbs.com/thread-5463766-1-1.html

http://www.360doc.com/content/12/0924/13/6828497_237893071.shtml
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

1

主题

4

帖子

0

精华

新手入门

积分
28
金钱
28
注册时间
2015-12-15
在线时间
0 小时
发表于 2015-12-15 17:47:41 | 显示全部楼层
不错、不错!!楼主讲的很细致,看了会思路清晰很多!
日敲代码上万行,冲进世界500强
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-15 18:02:46 | 显示全部楼层
回复【158楼】软件助理工程师_1:
---------------------------------
谢谢支持!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165516
金钱
165516
注册时间
2010-12-1
在线时间
2116 小时
发表于 2015-12-15 22:29:34 | 显示全部楼层
回复【159楼】229382777@qq.com:
---------------------------------
继续坚持。。。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-16 07:58:02 | 显示全部楼层
回复【160楼】正点原子:
---------------------------------
哈哈,必须的
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-17 20:55:21 | 显示全部楼层

实战:图片显示

 

代码解析:

基于前面几章的内容,本章还是用FAT对文件进行管理,首先是打开图片所在文件的目录,读取目录的信息,原子哥代码实现“f_opendir(&picdir,"0:/PICTURE")”,读取出磁盘0下的PICTURE文件夹的目录信息到picdir,接着从PICTURE文件夹下读出总的有效文件数,主要是读出有几张图片,原子哥代码得出有效文件数的程序如下(加了自己的备注):

[mw_shl_code=c,true]//得到path路径下,目标文件的总个数 //path:路径 //返回值:总有效文件数 u16 pic_get_tnum(u8 *path) { u8 res; u16 rval=0; DIR tdir; //临时目录 FILINFO tfileinfo; //临时文件信息 u8 *fn; res=f_opendir(&tdir,(const TCHAR*)path); //打开目录path,目录信息存至tdir tfileinfo.lfsize=_MAX_LFN*2+1; //长文件名最大长度 tfileinfo.lfname=mymalloc(tfileinfo.lfsize);//为长文件缓存区分配内存 if(res==FR_OK&&tfileinfo.lfname!=NULL) //打开目录成功,文件名不为空 { while(1)//查询总的有效文件数 { res=f_readdir(&tdir,&tfileinfo); //读取目录下的一个文件,文件信息存至tfileinfo /* 读取目录项,索引会自动下移 */ if(res!=FR_OK||tfileinfo.fname[0]==0)break; //错误了/到末尾了,退出 fn=(u8*)(*tfileinfo.lfname?tfileinfo.lfname:tfileinfo.fname);//支持长文件名 res=f_typetell(fn); //返回文件的类型 /*f_typetell函数里面通过一个二维数组指针FILE_TYPE_TBL,对写入的形参(文件名)进行比较判断, 返回文件后缀名相同的类型,将这个返回值前4位定位大区,后4位为小区,可以得知具体文件类型*/ if((res&0XF0)==0X50)//取高四位,看看是不是图片文件 { rval++;//有效文件数增加1 } } } return rval; } [/mw_shl_code]
接下是一个读取图片索引号放到数组里面的一个过程,这个数组主要方便后面获取图片信息使用
[mw_shl_code=c,true]//记录索引 res=f_opendir(&picdir,"0:/PICTURE"); //打开目录 if(res==FR_OK) { curindex=0;//当前索引为0 while(1)//全部查询一遍 { temp=picdir.index; //记录当前index res=f_readdir(&picdir,&picfileinfo);//读取目录(picfileinfo)下的一个文件(信息存至picdir) if(res!=FR_OK||picfileinfo.fname[0]==0)break; //错误了/到末尾了,退出 fn=(u8*)(*picfileinfo.lfname?picfileinfo.lfname:picfileinfo.fname);//长文件名 res=f_typetell(fn);//获取到文件类型,高四位表示所属大类,低四位表示所属小类. if((res&0XF0)==0X50)//取高四位,看看是不是图片文件 { picindextbl[curindex]=temp;//记录索引索引号 curindex++;//下一个图片文件 } } } [/mw_shl_code]

接着是初始化画图的函数” piclib_init();”,这个函数的内容比较简单,定义一个函数指针的结构体用于存放接口函数(读点、画点、填充、画线、颜色填充函数)和一个存放图片信息的结构体,里面对这两个结构体进行了赋值初始化,主要是方便后面调用。初始化完以后就可以打开图片所在的那个目录进行操作了,打开目录成功后原子哥代码有以下这段代码
[mw_shl_code=c,true]dir_sdi(&picdir,picindextbl[curindex]); //改变当前目录索引 res=f_readdir(&picdir,&picfileinfo); //读取目录下的一个文件 if(res!=FR_OK||picfileinfo.fname[0]==0)break; //错误了/到末尾了,退出 fn=(u8*)(*picfileinfo.lfname?picfileinfo.lfname:picfileinfo.fname); strcpy((char*)pname,"0:/PICTURE/"); //复制路径(目录) strcat((char*)pname,(const char*)fn); //将文件名接在后面 LCD_Clear(BLACK); ai_load_picfile(pname,0,0,lcddev.width,lcddev.height,1);//显示图片 Show_Str(2,2,240,16,pname,16,1); //显示图片名字 [/mw_shl_code]
这段代码用于得出图片所在路径及文件名,传送给“ai_load_picfile(pname,0,0,lcddev.width,lcddev.height,1);//显示图片”这个函数。这个函数是全部代码的中心,通过调入实参(图片文件名)和坐标、区域尺寸就可以在指定地方显示一张图片,该函数也比较简单,通过原子哥提供的读取文件类型的函数得出文件属于BMP/JPG/GIF这三个图片类型中的哪一种执行指定的解码函数,关键就是这个解码函数,解码函数的代码部分比较难,特别是JPG格式的,原子哥代码是用FATFS 的作者,提供了一个轻量级的JPG/JPEG解码库:TjpgDec,关于JPG/GIF的解码部分自己没有去细看,想着以后如果有需要再来细看。看了下原子哥提供的BMP的解码部分,相对来说难度没有那么大而且原子哥的备注做的很清晰,加上原子哥提供了资料“BMP图片文件详解”,解码函数中主要用以FAT的管理方式开辟内存,BMP的图片信息由“位图头文件数据结构”“位图信息数据结构”“调色板(有些不需要是因为真彩图用了调色板反而占更多存储空间)”“位图数据”,通过前面两部分可以得出图片的基本信息方便后面解码的操作,通过后面两部分可以通过调色板或直接24位颜色数据根据位图数据调用画点函数一个一个点显示出来。

下面附上一些资料方便日后查找细学

JPEG解码翻译:http://www.openedv.com/posts/list/23014.htm

JPEG解码优化:http://www.openedv.com/posts/list/13678.htm

TjpgDec技术手册.pdf

401.16 KB, 下载次数: 592

BMP图片文件详解.pdf

159.19 KB, 下载次数: 572

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-20 12:27:24 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2016-10-9 08:26 编辑

实战:IAP
IAP部分可参考我的另一篇帖子:
http://www.openedv.com/thread-78079-1-1.html

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165516
金钱
165516
注册时间
2010-12-1
在线时间
2116 小时
发表于 2015-12-20 23:05:10 | 显示全部楼层
顶起。。。。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

6

主题

130

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
437
金钱
437
注册时间
2013-4-9
在线时间
76 小时
发表于 2015-12-21 14:53:14 | 显示全部楼层
此贴无价!!!
签名必须手写!
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-22 10:19:39 | 显示全部楼层

实战:触控 USB 鼠标实验

 

USB 简介
USB ,是英文 Universal Serial BUS(通用串行总线)的缩写,而其中文简称为“通串线,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在 PC 领域的接口技术。USB 接口支持设备的即插即用和热插拔功能。 USB 是在 1994 年底由英特尔、康柏、 IBM、Microsoft 等多家公司联合提出的。

USB 发展到现在已经有 USB1.0/1.1/2.0/3.0 等多个版本。目前用的最多的就是 USB1.1 和USB2.0, USB3.0 目前已经开始普及。 STM32F103 自带的 USB 符合 USB2.0 规范。

标准 USB 共四根线组成,除 VCC/GND 外,另外为 D+,D-; 这两根数据线采用的是差分电压的方式进行数据传输的。在 USB 主机上, D-和 D+都是接了 15K 的电阻到低的,所以在没有设备接入的时候, D+、 D-均是低电平。而在 USB 设备中,如果是高速设备,则会在 D+上接一个 1.5K 的电阻到 VCC,而如果是低速设备,则会在 D-上接一个 1.5K 的电阻到 VCC。这样当设备接入主机的时候,主机就可以判断是否有设备接入,并能判断设备是高速设备还是低速设备。简单介绍一下 STM32 的 USB 控制器。

STM32F103 的 MCU 自带 USB 从控制器,符合 USB 规范的通信连接; PC 主机和微控制器之间的数据传输是通过共享一专用的数据缓冲区来完成的,该数据缓冲区能被 USB 外设直接访问。这块专用数据缓冲区的大小由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可使用 512 字节缓冲区(专用的 512 字节,和 CAN 共用),最多可用于 16 个单向或 8 个双向端点。 USB 模块同 PC 主机通信,根据 USB 规范实现令牌分组的检测,数据发送/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括 CRC 的生成和校验。

每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地址、大小和需要传输的字节数。当 USB 模块识别出一个有效的功能/端点的令牌分组时, (如果需要传输数据并且端点已配置)随之发生相关的数据传输。USB 模块通过一个内部的 16 位寄存器实现端口与专用缓冲区的数据交换。在所有的数据传输完成后,如果需要,则根据传输的方向,发送或接收适当的握手分组。在数据传输结束时, USB 模块将触发与端点相关的中断,通过读状态寄存器和/或者利用不同的中断来处理。

USB 的中断映射单元:将可能产生中断的 USB 事件映射到三个不同的 NVIC 请求线上
1、 USB 低优先级中断(通道 20):可由所有 USB 事件触发(正确传输, USB 复位等)。固件在处理中断前应当首先确定中断源。
2、 USB 高优先级中断(通道 19):仅能由同步和双缓冲批量传输的正确传输事件触发,目的是保证最大的传输速率。
3、 USB 唤醒中断(通道 42):由 USB 挂起模式的唤醒事件触发。


USB主要特征

● 符合USB2.0全速设备的技术规范
● 可配置18USB端点
CRC(循环冗余校验)生成/校验,反向不归零(NRZI)编码/解码和位填充
● 支持同步传输
● 支持批量/同步端点的双缓冲区机制
● 支持USB挂起/恢复操作
● 帧锁定时钟脉冲生成
注:USB和CAN共用一个专用的512字节的SRAM存储器用于数据的发送和接收,因此不能同时使用USB和CAN(共享的SRAM被USB和CAN模块互斥地访问)。USB和CAN可以同时用于一个应用中但不能在同一个时间使用。

USB模块实现了标准USB接口的所有特性,它由以下部分组成:

1.串行接口控制器(SIE)

2.定时器

3.分组缓冲器接口

4.端点相关寄存器

5.控制寄存器

6.中断寄存器

USB模块通过APB1接口部件与APB1总线相连, APB1接口部件包括以下部分:

1.分组缓冲区

2.仲裁器

3.寄存器映射单元

4.APB1封装

5.中断映射单元

框图如下:



调用的USB库部分



USB的内容确实比较复杂,我觉得学到这里就该分清专业了,前辈开发出了USB协议我们只要学会用就行了,如果日后专门从事这块的有必要去学习一下底层的部分如何驱动,作为初学者只要掌握移植就行了,日后如果常用再来仔细学下USB,现在的阶段是有个概念,了解如何移植

下面是一些资料,原子哥教程中提到关于USB库提供的8个参考例程,但是原子哥的光盘里面我没有找到,但是给了资料的下载地址,告知了ST的官网可以找到,文件名: STSW-STM32121.zip,附件在底下,这是ST的官网,资料这里面都有

http://www.stmcu.org/document/list/index/category-147


这章的程序部分原子哥有非常详细的讲解就不贴出来了,需要的可以看下

USB的“JoyStickMouse”例程结构分析(非常清晰):http://www.openedv.com/posts/list/411.htm

网友USB学习经验:http://www.openedv.com/posts/list/26963.htm

CD00158241.pdf

1.74 MB, 下载次数: 460

STSW-STM32121.zip

6.43 MB, 下载次数: 3186

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

3

主题

130

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
373
金钱
373
注册时间
2015-3-7
在线时间
43 小时
发表于 2015-12-22 16:23:18 | 显示全部楼层
楼主加油。学习的过程中,把自己的理解写成技术文档,方便以后自己回顾。
 希望把这样的习惯坚持下去,那么离大牛不远了~
为人莫作千年计,三十河东四十西,莫欺少年穷。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-22 16:30:21 | 显示全部楼层
回复【167楼】Mcu_learning:
---------------------------------
谢谢支持!!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

2

主题

56

帖子

0

精华

高级会员

Rank: 4

积分
509
金钱
509
注册时间
2015-12-22
在线时间
85 小时
发表于 2015-12-22 20:23:36 | 显示全部楼层
楼主很有毅力,支持!!!
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-26 13:33:45 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2015-12-26 14:21 编辑

学移植ucos前先了解下M3内核的一些知识
1.png
2.png
3.png
4.png
5.png
6.png
7.png
8.png
9.png
10.png


线程:有时被称为轻量级进程(Lightweight ProcessLWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程


PC(程序计数器):http://blog.sina.com.cn/s/blog_62714d6a0100mjgs.html
STM32启动文件详解:http://blog.chinaunix.net/uid-20734916-id-4812782.html
C语言的函数调用过程:http://blog.chinaunix.net/uid-23069658-id-3981406.html



我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

72

主题

2711

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3505
金钱
3505
注册时间
2014-8-4
在线时间
696 小时
发表于 2015-12-26 20:17:25 | 显示全部楼层
229382777@qq.co 发表于 2015-12-26 13:33
学移植ucos前先了解下M3内核的一些知识

good job
以我资质之鲁钝,当尽平心静气、循序渐进、稳扎稳打之力。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-28 11:58:00 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2015-12-28 12:02 编辑

实战:UCOS任务调度


1.png

任务优先级
这个概念比较好理解, ucos 中,每个任务都有唯一的一个优先级。优先级是任务的唯一标识。在 UCOSII 中,使用CPU 的时候,优先级高(数值小)的任务比优先级低的任务具有优先使用权,即任务就绪表中总是优先级最高的任务获得 CPU 使用权,只有高优先级的任务让出 CPU 使用权(比如延时)时,低优先级的任务才能获得 CPU 使用权。UCOSII 不支持多个任务优先级相同,也就是每个任务的优先级必须不一样。
任务堆栈
就是存储器中的连续存储空间。为了满足任务切换和响应中断时保存 CPU 寄存器中的内容以及任务调用其他函数时的需要,每个任务都有自己的堆栈。在创建任务的时候,任务堆栈是任务创建的一个重要入口参数。
2.png


任务控制块OS_TCB
用来记录任务堆栈指针,任务当前状态以及任务优先级等任务属性。UCOSII 的任何任务都是通过任务控制块( TCB)的东西来控制的,一旦任务创建了,任务控制块 OS_TCB 就会被赋值。
每个任务管理块有 3 个最重要的参数: 1,任务函数指针; 2,任务堆栈指针; 3,任务优先级;任务控制块就是任务在系统里面的身份证( UCOSII 通过优先级识别任务)。
3.png

任务就绪表
简而言之就是用来记录系统中所有处于就绪状态的任务。它是一个位图,系统中每个任务都在这个位图中占据一个进制位,该位置的状态( 1 或者 0)就表示任务是否处于就绪状态。
4.png

任务调度
任务调度的作用一是在任务就绪表中查找优先级最高的就绪任务,二是实现任务的切换。
比如说,当一个任务释放 cpu 控制权后,进行一次任务调度,这个时候任务调度器首先要去任
务就绪表查询优先级最高的就绪任务,查到之后,进行一次任务切换,转而去执行下一个任务。

5.png
6.png

UCOS的五种状态
7.png

建立任务函数
OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTUprio)
该函数包括4 个参数:task:是指向任务代码的指针; pdata:是任务开始执行时,传
递给任务的参数的指针; ptos:是分配给任务的堆栈的栈顶指针; prio 是分配给任务的优
先级。

任务删除函数
INT8UOSTaskDel(INT8U prio)
其中参数prio 就是我们要删除的任务的优先级,可见该函数是通过任务优先级来实现
任务删除的。

请求任务删除函数
INT8UOSTaskDelReq(INT8U prio)

改变任务的优先级函数
INT8UOSTaskChangePrio(INT8U oldprio,INT8U newprio)

任务挂起函数
INT8UOSTaskSuspend(INT8U prio)

任务恢复函数
NT8UOSTaskResume(INT8U prio)

STM32上面运行UCOSII的步骤:
1 移植 UCOSII
2 编写任务函数并设置其堆栈大小和优先级等参数。
3 初始化 UCOSII,并在 UCOSII 中创建任务
4 启动 UCOSII
8.png

[mw_shl_code=applescript,true]//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{                                   
          OSIntEnter();                //进入中断,用来记录中断嵌套层数
    OSTimeTick();   //调用ucos的时钟服务程序,系统时钟节拍服务函数,在每个时钟节拍了解每个任务的延时状态,使已经到达延时时限的非挂起任务进入就绪状态;               
OSIntExit();    //触发任务切换软中断,退出中断服务函数,该函数可能触发一次任务切换

}
[/mw_shl_code]
9.png
10.png
11.png


最后看下PendSV(可悬起系统调用)的知识,这个中断在任务调度的时候起着非常重要的作用
12.png
13.png
14.png
15.png


这些都是ucos的基本知识,原子哥的教程讲的不是很详细,但是提供了很多非常好的资料,“ucOS-II入门教程(任哲)”这个PPT讲的非常好,简单描述了UCOS的原理,我看了2遍,还有一份资料也很好,叫“ucosiiSTM32上的移植详解”这个我也看了三遍,对ucos有了个大概的理解,以下附上资料,原子哥的光盘中也有


ucosii在STM32上的移植详解.pdf

207.97 KB, 下载次数: 2133

我的博客:http://blog.csdn.net/itdo_just
回复 支持 1 反对 0

使用道具 举报

12

主题

72

帖子

0

精华

高级会员

Rank: 4

积分
504
金钱
504
注册时间
2015-9-9
在线时间
59 小时
发表于 2015-12-28 15:01:20 | 显示全部楼层
真心羡慕楼主的坚持。加油哦
毕业两年才学STM32...
这些都没关系,重点是自己到底成长了没有
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-28 15:08:36 | 显示全部楼层
WM_CH 发表于 2015-12-28 15:01
真心羡慕楼主的坚持。加油哦

谢谢支持!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-29 14:04:51 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2015-12-29 14:26 编辑

实战:UCOS信号量和邮箱

1.png
事件:
2.png
3.png

事件控制块结构体定义如下:
4.png

信号量:
5.png
UCOSII将二值型信号量称之为也叫互斥型信号量,将 N 值信号量称之为计数型信号量,也就是普通的信号量。

任务优先级反转:
在可剥夺型内核中,当任务以独占方式使用共享资源时,会出现低优先级任务先于高优先级任务而被运行的现象,这种现象叫做任务优先级反转。
在一般情况下是不允许出现这种任务优先级反转现象的。使用信号量的任务是否能够运行是受任务的优先级别和是否占用信号量两个条件约束的,
而信号量的约束高于优先级别的约束。于是当出现低优先级别的任务与高优先级别的任务使用同一个信号量,而系统中还存有别的中等优先级别
的任务时,如果低优先级别的任务先获得了信号量,就会使高级别的任务处于等待状态,而那些不使用该信号量的中等级别的任务却可以剥夺低
优先级别的任务的CPU使用权而先于高优先级别的任务而运行了。 解决问题的办法之一,是使获得信号量任务的优先级别在使用共享资源期间暂
时提升到所有任务最高优先级的高一个级别上,以使该任务不被其他的任务所打断,从而能尽快地使用完共享资源并释放信号量,然后在释放了
信号量之后再恢复该任务原来的优先级别。

1 创建信号量函数
[mw_shl_code=applescript,true]OS_EVENT *OSSemCreate (INT16U cnt)[/mw_shl_code]
该函数返回值为已创建的信号量的指针,而参数 cnt 则是信号量计数器( OSEventCnt)的初始值。

2 请求信号量函数
[mw_shl_code=applescript,true]void OSSemPend( OS_EVENT *pevent, INT16U timeout, INT8U *err)[/mw_shl_code]
其中,参数pevent 是被请求信号量的指针, timeout 为等待时限, err 为错误信息。

3 发送信号量函数(释放信号量也叫做发送信号量)
[mw_shl_code=applescript,true]INT8U OSSemPost(OS_EVENT *pevent)。[/mw_shl_code]
其中,pevent为信号量指针,该函数在调用成功后,返回值为OS_ON_ERR,否则会根据具体错误返回OS_ERR_EVENT_TYPE OS_SEM_OVF
OSSemPost函数在对信号量的计数器操作之前,首先要检查是否还有等

4 删除信号量函数
[mw_shl_code=applescript,true]OS_EVENT *OSSemDel (OS_EVENT *pevent,INT8U opt, INT8U *err)[/mw_shl_code]
其中,pevent 为要删除的信号量指针, opt 为删除条件选项, err 为错误信息。

信号邮箱:

6.png

1 创建邮箱函数
[mw_shl_code=applescript,true]OS_EVENT*OSMboxCreate(void *msg)[/mw_shl_code]
函数中的参数msg 为消息的指针,函数的返回值为消息邮箱的指针。

2 向邮箱发送消息函数
[mw_shl_code=applescript,true]INT8U OSMboxPost(OS_EVENT *pevent,void *msg)[/mw_shl_code]
其中pevent 为消息邮箱的指针, msg 为消息指针。

3 请求邮箱函数
[mw_shl_code=applescript,true]void*OSMboxPend (OS_EVENT *pevent, INT16U timeout,INT8U *err)[/mw_shl_code]
其中pevent 为请求邮箱指针, timeout 为等待时限, err 为错误信息。

4 查询邮箱状态函数
[mw_shl_code=applescript,true]INT8U OSMboxQuery(OS_EVENT*pevent,OS_MBOX_DATA *pdata)[/mw_shl_code]
其中pevent 为消息邮箱指针, pdata 为存放邮箱信息的结构。

5 删除邮箱函数
[mw_shl_code=applescript,true]OS_EVENT*OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err)[/mw_shl_code]
其中pevent为消息邮箱指针, opt 为删除选项, err 为错误信息。

代码部分:
原子哥代码中创建了一个消息邮箱,一个信号量
OS_EVENT * msg_key;    //按键邮箱事件块指针
OS_EVENT* sem_led1;   //LED1信号量指针

在按键扫描任务中往消息邮箱发送消息
key=KEY_Scan(0);   
if(key)OSMboxPost(msg_key,(void*)key);//发送消息

然后在按键处理程序中请求消息邮箱的数据后用switch进行判断
key=(u32)OSMboxPend(msg_key,10,&err);

switch下如果从key获取到的消息为KEY0,则发送led1的信号量
semmask=1;
OSSemPost(sem_led1);

里面进行信号量发送函数,信号量函数进行了信号量的累加
pevent->OSEventCnt++;   /* Increment semaphore count to registerevent   */

led1的任务里面则有个接收信号量的接收函数
OSSemPend(sem_led1,0,&err); //第二个实参设置为0则表明任务的等待时间为无限长
//如果按键KEY0一直没有按下则该任务由于没有收到信号量而一直处于等待状态得不到执行,直到KEY0按下
//OS_EventTaskWait(pevent);//OSSemPend函数中这一句起到了关键作用,如果没有信号量等待时限又为0则等待下一个高优先级任务
LED1=0;
delay_ms(200);
LED1=1;
delay_ms(800);

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

6

主题

36

帖子

0

精华

初级会员

Rank: 2

积分
113
金钱
113
注册时间
2015-12-23
在线时间
14 小时
发表于 2015-12-29 16:02:36 | 显示全部楼层
楼主加油,如果记的这些能完全掌握的话,能力已经很强了
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-29 16:16:47 | 显示全部楼层
MagicYang 发表于 2015-12-29 16:02
楼主加油,如果记的这些能完全掌握的话,能力已经很强了

谢谢支持~~
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

6

主题

40

帖子

0

精华

初级会员

Rank: 2

积分
142
金钱
142
注册时间
2013-3-20
在线时间
11 小时
发表于 2015-12-29 17:26:17 | 显示全部楼层
顶!!!
微博Sweeneycc
回复 支持 反对

使用道具 举报

11

主题

65

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
308
金钱
308
注册时间
2015-10-7
在线时间
59 小时
发表于 2015-12-29 20:01:31 | 显示全部楼层
牛逼啊
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165516
金钱
165516
注册时间
2010-12-1
在线时间
2116 小时
发表于 2015-12-29 22:20:16 | 显示全部楼层
多向楼主学习...
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

8

主题

55

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
462
金钱
462
注册时间
2015-12-30
在线时间
76 小时
发表于 2015-12-30 13:18:14 | 显示全部楼层
hexboot 发表于 2015-11-12 00:40
搞电子态度很重要,学历真是浮云.

我觉得也是,学历真不代表什么
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
25
金钱
25
注册时间
2013-10-23
在线时间
2 小时
发表于 2015-12-30 17:24:50 | 显示全部楼层
真的要楼主学习,这是一种态度,一种坚持,加油!
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-31 08:48:54 | 显示全部楼层
yigeng0820 发表于 2015-12-30 17:24
真的要楼主学习,这是一种态度,一种坚持,加油!

谢谢支持,共勉!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-31 09:28:18 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2015-12-31 09:30 编辑

实战:消息队列、信号量、软件定时器

这章的内容原子哥的教程讲的非常详细,所以就贴上些代码实现部分

消息队列:
1.png
2.png

1) 创建消息队列函数
OS_EVENT*OSQCreate(void**start,INT16U size)
其中,start为存放消息缓冲区指针数组的地址,size 为该数组大小。该函数的返回值为消息队列指针。

2) 请求消息队列函数
void*OSQPend(OS_EVENT*pevent,INT16Utimeout,INT8U *err)
其中,pevent为所请求的消息队列的指针, timeout 为任务等待时限,err 为错误信息。

3) 向消息队列发送消息函数
INT8UOSQPost(OS_EVENT*pevent,void *msg)INT8U OSQPost(OS_EVENT*pevent,void*msg)
其中,pevent 为消息队列的指针, msg 为待发消息的指针。

信号量集
3.png
4.png
5.png

1) 创建信号量集函数
OS_FLAG_GRP*OSFlagCreate (OS_FLAGS flags,INT8U *err )
其中,flags 为信号量的初始值(即 OSFlagFlags 的值), err 为错误信息,返回值为该信号量集
的标志组的指针,应用程序根据这个指针对信号量集进行相应的操作。

2) 请求信号量集函数
OS_FLAGSOSFlagPend(OS_FLAG_GRP*pgrp, OS_FLAGS flags, INT8U wait_type, INT16U timeout,INT8U *err)
其中,pgrp 为所请求的信号量集指针, flags 为滤波器(即OSFlagNodeFlags 的值),wait_type
为逻辑运算类型 (即 OSFlagNodeWaitType 的值),timeout为等待时限,err 为错误信息。

3) 向信号量集发送信号函数
OS_FLAGSOSFlagPost (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U *err)
其中,pgrp 为所请求的信号量集指针, flags 为选择所要发送的信号, opt 为信号有效选项,
err 为错误信息。
所谓任务向信号量集发信号,就是对信号量集标志组中的信号进行置“ 1”(置位)或
置“ 0”(复位)的操作。至于对信号量集中的哪些信号进行操作,用函数中的参数 flags
来指定;对指定的信号是置“ 1”还是置“0”,用函数中的参数 opt 来指定( opt =
OS_FLAG_SET 为置“ 1”操作;opt = OS_FLAG_CLR 为置“ 0”操作)。

软件定时器
6.png
1) 创建软件定时器函数
OS_TMR*OSTmrCreate(INT32U dly, INT32U period, INT8U opt, OS_TMR_CALLBACKcallback,void*callback_arg, INT8U *pname, INT8U *perr)
Dly:用于初始化定时时间,对单次定时( ONE-SHOT 模式)的软件定时器来说,这就是该定时器的定时时间,
     而对于周期定时( PERIODIC 模式)的软件定时器来说,这是该定时器第一次定时的时间,从第二次开始定时时间变为 period。
period:在周期定时(PERIODIC 模式),该值为软件定时器的周期溢出时间。
Opt:用于设置软件定时器工作模式。可以设置的值为: OS_TMR_OPT_ONE_SHOT或 OS_TMR_OPT_PERIODIC,如果设置为前者,
     说明是一个单次定时器;设置为后者则表示是周期定时器。
callback:为软件定时器的回调函数,当软件定时器的定时时间到达时,会调用该函数。
callback_arg:回调函数的参数。
pname:为软件定时器的名字。

2) 开启软件定时器函数
BOOLEANOSTmrStart (OS_TMR *ptmr, INT8U *perr)
其中ptmr 为要开启的软件定时器指针, perr为错误信息。

3) 停止软件定时器函数
BOOLEANOSTmrStop (OS_TMR *ptmr,INT8U opt,void *callback_arg,INT8U *perr)
其中ptmr 为要停止的软件定时器指针。
opt 为停止选项,可以设置的值及其对应的意义为:
    OS_TMR_OPT_NONE,直接停止,不做任何其他处理
    OS_TMR_OPT_CALLBACK,停止,用初始化的参数执行一次回调函数
    OS_TMR_OPT_CALLBACK_ARG,停止,用新的参数执行一次回调函数
callback_arg:新的回调函数参数。
perr:错误信息



下面附上关于软件定时器的资料,需要的可以看下

深入理解和实现RTOS_连载10_软件定时器概念.pdf

400 KB, 下载次数: 157

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

10

主题

41

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
247
金钱
247
注册时间
2015-3-19
在线时间
52 小时
发表于 2015-12-31 14:42:26 | 显示全部楼层
很赞,支持你
回复 支持 反对

使用道具 举报

16

主题

73

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
214
金钱
214
注册时间
2015-12-21
在线时间
18 小时
发表于 2015-12-31 14:59:56 | 显示全部楼层
赞一个,即使照着抄一遍也能学习不少东西
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2015-12-31 15:19:56 | 显示全部楼层
zzu65 发表于 2015-12-31 14:59
赞一个,即使照着抄一遍也能学习不少东西

谢谢支持!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2016-1-3 12:10:32 | 显示全部楼层
最近在看下面的两篇文章,考虑稳定性虽然对于实际项目不大可能用自己写的RTOS,但是学习了解以下的文章非常有利于了解RTOS的原理
http://www.openedv.com/forum.php?mod=viewthread&tid=11005

简易0S设计.pdf

374.41 KB, 下载次数: 551

我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2016-1-6 10:48:18 | 显示全部楼层
经过两个月的学习,思路清晰了很多,发现如果用心学的话其实没有什么是解决不了的,接下来开始准备投简历找工作了,这个贴如果后续有不错的资料还会传上来
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2016-1-6 16:00:08 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2016-1-6 16:05 编辑

感觉坛友这两篇关于汇编的文档写的不错,分享给大家,建议看一下
原网址在:http://www.openedv.com/forum.php?mod=viewthread&tid=45361

Cortex-M3汇编实践.pdf

1.08 MB, 下载次数: 6101

浅谈STM32汇编(Wizard).pdf

5.24 MB, 下载次数: 10244

我的博客:http://blog.csdn.net/itdo_just
回复 支持 4 反对 0

使用道具 举报

3

主题

7

帖子

0

精华

新手上路

积分
39
金钱
39
注册时间
2016-1-6
在线时间
6 小时
发表于 2016-1-6 16:07:50 | 显示全部楼层
建议楼主在顶楼设置目录,方便大家浏览
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2016-1-6 16:12:27 | 显示全部楼层
本帖最后由 229382777@qq.com 于 2016-1-6 16:13 编辑
skywk0913 发表于 2016-1-6 16:07
建议楼主在顶楼设置目录,方便大家浏览

恩,我后期补上
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

5

主题

38

帖子

0

精华

初级会员

Rank: 2

积分
133
金钱
133
注册时间
2015-1-18
在线时间
17 小时
发表于 2016-1-7 10:19:23 | 显示全部楼层
楼主加油
回复 支持 反对

使用道具 举报

5

主题

38

帖子

0

精华

初级会员

Rank: 2

积分
133
金钱
133
注册时间
2015-1-18
在线时间
17 小时
发表于 2016-1-7 10:19:54 | 显示全部楼层
看好你哦, 会关注你的, 交流找工作的情况~~~
回复 支持 反对

使用道具 举报

5

主题

38

帖子

0

精华

初级会员

Rank: 2

积分
133
金钱
133
注册时间
2015-1-18
在线时间
17 小时
发表于 2016-1-7 12:25:37 | 显示全部楼层
楼主, 这是我学习51时候的笔记,上拉电阻那里, 看来咱们算是师出同门啦 ????
QQ拼音截图未命名.png
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2016-1-7 13:30:37 | 显示全部楼层
myb13149251 发表于 2016-1-7 12:25
楼主, 这是我学习51时候的笔记,上拉电阻那里, 看来咱们算是师出同门啦 ????

你是看宋老师教程的吧,我学单片机是看宋老师的视频入门的,这些知识都是那里学来的
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

9

主题

108

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1136
金钱
1136
注册时间
2013-7-16
在线时间
80 小时
发表于 2016-1-7 14:27:23 | 显示全部楼层
佩服楼主的精神, 希望能坚持!
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
 楼主| 发表于 2016-1-7 14:29:38 | 显示全部楼层
cookles 发表于 2016-1-7 14:27
佩服楼主的精神, 希望能坚持!

共勉!!!!!
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

12

主题

98

帖子

0

精华

高级会员

Rank: 4

积分
777
金钱
777
注册时间
2012-4-23
在线时间
66 小时
发表于 2016-1-7 20:08:13 | 显示全部楼层
好方法,学习致用!
我不相信命运,人定胜天!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-5-18 23:17

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表