1.stm32的union中的数据为大端模式
大端模式:便于阅读
小端模式:便于存储
.....
union T{
u16 ch16;
u8 ch8;
}
union T t;
t.ch16=1234;
.....
log(t.ch8[0]);//34 0x2000CBD0 低地址 高位数字
log(t.ch8[1]);//12 0X2000CBD1 高地址 低位数字
......
2.指针没有分配内存时,不能进行strcat和strncpy等操作,为了节省内存,可以动态malloc分配,而静态数组分配太过占据内存的情况。
.....
char *p,temp;
char ne[10]={0};
char cp[10]="1234569789";
temp=&cp[2];
strncpy(p,temp,3);
strcat(ne,p);//输出结果为不可控
//可以先为p分配内存
p=malloc(4);
strncpy(p,temp,3);
strcat(ne,p);//就会ne=“345”
free(p);//释放内存,节省堆空间,就是启动文件中的heap大小
p=NULL;//此处是防止p成为悬空指针
3.ITM调试打印输出
要求:内核支持ITM,硬件需要J-link的swo与PB3连接。时钟何数据正常连接。swo就是你打印输出的数据。DEBUG中需要在jlink中开启Trace功能。配置代码:
.....
//ITM:指令跟踪宏单元
//printf函数重新绑定输出
int fputc(int ch,File *f)
{
ITM_Send_Char(ch);//此函数默认绑定ITM 的port0
return 0;
}
......
//一般我们都用串口直接打印,ITM需要硬件支持和支持的烧录外设,如jlink,stlink。
debug时打开View-》serial_window->debug(printf)view窗口就可以看到数据输出了。
4.函数指针
...
int function(char *ch)
{
return 1;
}
.....
typedef int (*function_types)(char *argc);//声明指针类型
function_types function_point;//定义一个函数指针
function_point=function;//绑定函数
function_point();//调用函数
......
可以更好的规划程序框架,高内聚,低耦合
5,字符串转与数值互转
.....
sscanf(str,%d,&num);//将字符串str转为数值存放到num
....
sprintf(str,%d,num);//将数值转成字符串存放到str
....
//直接转
char str[1]="5";
int num=(int)str[0]-'0';
//标准库
#include《stdlib.h》
char str[]="526";
int num=atoi(str);
....
//数子转字符串
int y=1456;
str[0]=y/1000+'0';
y=y%1000;
str[1]=y/100+'0';
y=y%100;
str[2]=y/10+'0';
y=y%10;
str[3]=y+'0';
//先要判断数字的位数,可以用范围法,但是大于u64就比较复杂了,需要自己去拼凑更大数值
6.搞底层的,可能会经常遇到拼接字符串的情况,现在我们可以利用这个库函数来实现;
#include "stdio.h"
#define maxlength 128
#define Debug 1
int main(int argc,char *argv[]){
char cmd[]="AT+CIPSTART=%d,TCP,%s,%d\r\n";//连接到服务器指令
//此条指令可以发现很多数据没有填充,那么怎么填充呢?
int link_id=1;
char ip[]="www.dshuibi.com";
int port=666;
char post_cmd[maxlength];
snprintf(post_cmd,sizeof(post_cmd),cmd,link_id,ip,port);
//注意,snprintf(转化后放的,转化后的大小,格式字符串,替换字符,替换字符);
#if Debug
printf("%s",post_cmd);//AT+CIPSTART=1,TCP,www.dshuibi.com,666
#endif
//这样就拼凑成功了,当然也可以自己写函数
return 0;
}
//那么反过来是如何操作的,从字符串取值呢?
char fmt[]=“recived:%d,%d:\r\n”;
char data[]="recived:10,28:nice to meet you,my frien!\r\n";
int type,length;
sscanf(data,fmt,&type,&length);//type=10,length=28
完成字符串特定格式数据的提取。
7.地址转指针再转函数
//此代码stm32和GD32都可以
#define Start_addar 0x0800 0000
typedef void(*function_pointer)(void);
.....
function_pointer fun_pointer;
fun——pointer=(function_pointer)*(u32*)Start_addar;//先将地址转成地址指针,再解指针取值,再将值转成函数指针。
fun_pointer();//调用函数
.......
/*在单片机裸机中,想搞定地址变程序跳转,需要注意这几点*/
1.主堆栈指针,一般是MSP,他的值就是Start_addar;
2.跳转函数,它的值一般是Start_addar+4
3.屏蔽所有中断
4.在跳转过去的程序中开启中断,向量表重映射
//boot
...
__set_PRIMASK(1);//屏蔽所有中断
if(((*(vu32*)APP_Address)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
{
jump2app=(IAP_Fun)*(vu32*)(APP_Address+4); //用户代码区第二个字为程序开始地址(复位地址)
__set_MSP(*(vu32*)APP_Address); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app(); //跳转到APP.
}
....
//跳转过去程序
....
__set_PRIMASK(0);
SCB->VTOR=Start_addar;//向量表重映射
...
可以发现,一个程序它在flash中的地址是0x0800 0000+offset,它必然要被加载到0x2000 0000,这个区域就是程序堆栈区,*(vu*)Start_addar & 0x2ffe 0000=0x2000 0000。0x0800 0000存储的是主栈(MSP)地址,0x0800 0004存储的是程序起始地址。
学过51的都知道,程序的执行就是在几个寄存器(sp.pc,r0~r7)中不停的循环。那么这个主栈MSP就是(sp,pc,r0~r15),这几个寄存器的位置和值的声明,然后pc+1,程序就从起始地址开始跑起来了。
8.主栈msp的值必然是0x2000 0000,那么在freertos中呢?
freertos中每个程序都有自己的栈,一般一个数组定义它。它是从msp转成sp的。
9.关于数组初始化的问题:
全局数组,如果没有初始化,那么他的值为0,但是如果是非静态的局部数组或者函数内部的数组,那么他的值就是随机的。
数组初始化的方法;
int array[10]={0};
memset(array,0,sizeof(array));
char str[100]="abcdef";
char *strp="acdef";
注意:
0在字符数组的含义比较特殊,字符串处理函数检测到0默认结束。这个0是 char c=0;而不是 char c1='0';
char p='0';//p=48
char p=0;
比如可见字符范围:0x20~0x7e 空格sp和~
10.互斥的概念
int mutxe;
mutxe=0;
char get_mutxe()
{
if(mutxe!=1)
{
mutxe=1;
return 1;
}
else
return 0;
}
void free_mutxe()
{
mutxe=0;
}
void task1()
{
if(1!=get_mutxe())//获得锁
return;
............
free_mutxe();//释放锁
}
void task2()
{
if(1!=get_mutxe())
return;
............
free_mutxe();
}
void main()
{
task1;
}
void isr()
{
task2;
}
在freertos中,获得锁会行为阻碍其它更高优先级的任务阻塞,直到别的任务释放锁它才能继续运行。本质是将低优先级拉到和高优先级同一个优先级。
11.一些很容易告糊涂的程序;
switch(step){
case 1:
printf("这是老1");
case 2:
printf("这是老2");
case 3:
break;
case 4:
printf("这是老4");
case 5:
break;
}
step=1时输出:
这是老1
这是老2
step=4时输出:
printf("这是老4");
step=3时没有输出
switch(step)
{
case 1: do {step++;
case 2:step++;
case 3:step++;
case 4:step++;
case 5:}while(step<10);
}
step=3时调用此函数输出什么?
12,stm32中的void *
它叫万能指针,可以转换成任何类型的指针,一般在不明确调用者的类型时。比如:
void * pobject;
//object可能是个基本类型,也可能是个结构体。甚至是个复杂结构体。
13.宏定义判断与预定义判断
//一定要分清楚#ifdef 和#if的区别
#define hong_x 0
#ifdef hong_x //这个宏有没有定义,只要说了就是定义了,不管它的值是不是0
printf("宏已经定义");
#else
printf("宏未定义")
#endif
#if hong_x //判断结果为真才会执行,0的结果为假
printf("结果为真")
#else
printf("结果为假")
#endif
输出结果:
宏已经定义
结果为假
//文件防止重复加载
#ifndef _xxx_h
#define _xxx_h
....
#endif
14,预编译排错
#include <assert.h>
void assert(int expression);//如果表达式为0,编译器会输出当前位置,终止程序运行。非0程序继续执行。非常好用,不确定的队列,链表,计算,类型等判断都可以使用
#error 直接让编译输出
#if nodebug
#error "This no debug"
#else
.......
#endif
//查错和开发时常用,但是在正式版本一般关闭,会消耗额外的资源和数据
15.三角函数的使用
#include "math.h"
#include "stdio.h"
void main(void){
double a,b,c=5;
double pai=3.1415926;
double angle=30;
a=c*sin(30*pai/180);
b=c*cos(30*pai/180);
printf("a=%d;b=%d",a,b);
}
//一定要把角度换算成弧度