[讨论] 【直播+开源毕设】基于STM32F4的网络收音机设计与实现

人民币的幻想   2015-4-14 13:28 楼主
        自从考研复试结束以后,就开始了俺的毕业设计了。这个题目说实话,想做出来简单,想做好比较难。好了,言归正传,开始说说设计思路吧。

      硬件方案如下:
1.主控制器采用ST公司的STM32F407ZGT6,该芯片基于Cortex M4内核,带有FPU浮点运算单元,和DSP指令集。主频168MHZ,拥有192KB片内SRAM和1MB的Flash空间
2.显示部分采用单色12864液晶,采用此种方案主要在于网络收音机并不需要显示彩色图片,仅仅显示状态即可。综合考虑,此液晶性价比较高。
3.解码部分:采用了欧胜公司(Wolfson)的WM8978。作为一个音频输出设备,音质是第一要考虑的, WM8978是Wolfson新近推出的一款全功能音频处理器。它带有一个HI-FI级数字信号处理内核,支持增强3D硬件环绕音效,以及5频段的硬件均衡器,可以有效改善音质;并有一个可编程的陷波滤波器,用以去除屏幕开、切换等噪音。WM8978同样集成了对麦克风的支持,以及用于一个强悍的扬声器功放,可提供高达900mW的高质量音响效果扬声器功率。一个数字回放限制器可防止扬声器声音过载。WM8978进一步提升了耳机放大器输出功率,在推动16欧姆耳机的时候,每声道最大输出功率高达40毫瓦。
4.网络PHY芯片:采用LAN8720,由于STM32F407ZGT6本身自带MAC层,因此实现网络通信只需要加一片PHY芯片即可。PHY芯片通过RMII接口和主控MAC通信,主控通过SMI接口配置PHY芯片。
5.字库及存储:SD卡,FLASH芯片,EEPROM。
软件方案如下:
1.操作系统选用UCOSII,实时性好。
2.操作界面移植STemwin负责某些复杂人机界面实现。
3.文件系统采用znFAT,用来读取SD卡和Flash芯片中的数据。
4.音频解码库采用Libmad开源软件库。(libmad)是一个开源的高精度 MPEG 音频解码库,支持 MPEG-1(Layer I, Layer II 和 LayerIII(也就是 MP3)。LIBMAD 提供 24-bit 的 PCM 输出,完全是定点计算,非常适合没有浮点支持的平台上使用。使用 libmad 提供的一系列 API,就可以非常简单地实现 MP3 数据解码工作。
5.网络协议栈采用LwIP。LwIP是LightWeight (轻型)IP协议,有无操作系统的支持都可以运行。LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。
PS:本人所用的硬件平台是正点原子出的探索者M4开发板。一方面由于这块板上的硬件刚好够做这个题目,另一方面是手头资金实在不充裕。因此也就暂时没有画自己的硬件板。
作为一个菜逼,干货并没有多少。唯一会的就是水,所以回帖水分大。望见谅!

回复评论 (27)

驱动LCD12864画点,显示字符和汉字

今天的进度是把12864液晶显示字符,汉字,画点函数搞定了。
常规驱动12864有以下几种实现:
1. IO模拟80并口。
2. FSMC总线驱动
3. IO模拟串行驱动
4. 硬件SPI驱动
暂时选用的IO模拟80并口方式,这种办法较为简单,易于实现。下面详述此方案:
受限于板子的IO分配,液晶和MCU引脚连接如下:
/*************************************************************************************
LCD12864
LCD_RS PB1
LCD_RW PE3
LCD_EN PE4
LCD_PSB PE5
LCD_RST 接到板子RST上
LCD_D0 PE6
LCD_D1 PF6
LCD_D2 PF11
LCD_D3 PG6
LCD_D4 PG7
LCD_D5 PG8
LCD_D6 PG12
LCD_D7 PG15
LCD_BL PB0
************************************************************************************/
由于分配了不连续的IO,因此读写数据函数的实现比往常困难一点,不过只要熟悉C语言的位运算,这都不叫事~~。
//数据格式
//D7 D6 D5 D4 D3 D2 D1 D0
uint8_t ReadByteFromLCD(void)
{
uint8_t res=0;
res=(LCD_D0_IN<<0)|(LCD_D1_IN<<1)
|(LCD_D2_IN<<2)|(LCD_D3_IN<<3)
|(LCD_D4_IN<<4)|(LCD_D5_IN<<5)
|(LCD_D6_IN<<6)|(LCD_D7_IN<<7);
returnres;
}
void WriteByteToLCD(uint8_t byte)
{
LCD_D0_OUT=(byte&0x01)>>0;
LCD_D1_OUT=(byte&0x02)>>1;
LCD_D2_OUT=(byte&0x04)>>2;
LCD_D3_OUT=(byte&0x08)>>3;
LCD_D4_OUT=(byte&0x10)>>4;
LCD_D5_OUT=(byte&0x20)>>5;
LCD_D6_OUT=(byte&0x40)>>6;
LCD_D7_OUT=(byte&0x80)>>7;
}
以上就是读写函数的实现,不过不要忘了在读数据之前将IO口设为输入,写数据之前将IO口设为输出。
/*************************************************************************************
LCD判忙函数
1:忙
0:不忙
************************************************************************************/
uint8_t LCD_Busy(void)
{
uint8_t res=0;
LCD_RS = 0;
LCD_RW = 1;
LCD_EN = 1;
//数据线IO方向设定
GPIO_Set(GPIOE,PIN6,GPIO_MODE_IN,0,0,GPIO_PUPD_PU);
GPIO_Set(GPIOF,PIN6|PIN11,GPIO_MODE_IN,0,0,GPIO_PUPD_PU);
GPIO_Set(GPIOG,PIN6|PIN7|PIN8|PIN12|PIN15,GPIO_MODE_IN,0,0,GPIO_PUPD_PU);
delay_us(5);
//读数据
res=(ReadByteFromLCD()&0x80);
LCD_EN = 0;
return res;
}
12864液晶和高速MCU通信的时候需要注意检测忙信号,以免导致错误的发生。接下来就需要实现写数据/指令到LCD的函数了。
//写指令/数据到LCD
void WriteToLCD(uint8_t mode,uint8_t byte)
{
if(mode==LCD_CMD)//写指令
{
while(LCD_Busy());
LCD_RS = 0;//指令
LCD_RW = 0;//写
LCD_EN = 0;
delay_us(5);
GPIO_Set(GPIOE,PIN6,GPIO_MODE_OUT,GPIO_OTYPE_OD,GPIO_SPEED_100M,GPIO_PUPD_PU);
GPIO_Set(GPIOF,PIN6|PIN11,GPIO_MODE_OUT,GPIO_OTYPE_OD,GPIO_SPEED_100M,GPIO_PUPD_PU);
GPIO_Set(GPIOG,PIN6|PIN7|PIN8|PIN12|PIN15,GPIO_MODE_OUT,GPIO_OTYPE_OD,GPIO_SPEED_100M,GPIO_PUPD_PU);
WriteByteToLCD(byte);//写
delay_us(5);
LCD_EN = 1;
delay_us(5);
LCD_EN = 0;
}else //写数据
{
while(LCD_Busy());
LCD_RS = 1;//数据
LCD_RW = 0;
LCD_EN = 0;
delay_us(5);
GPIO_Set(GPIOE,PIN6,GPIO_MODE_OUT,GPIO_OTYPE_OD,GPIO_SPEED_100M,GPIO_PUPD_PU);
GPIO_Set(GPIOF,PIN6|PIN11,GPIO_MODE_OUT,GPIO_OTYPE_OD,GPIO_SPEED_100M,GPIO_PUPD_PU);
GPIO_Set(GPIOG,PIN6|PIN7|PIN8|PIN12|PIN15,GPIO_MODE_OUT,GPIO_OTYPE_OD,GPIO_SPEED_100M,GPIO_PUPD_PU);
WriteByteToLCD(byte);//写
delay_us(5);
LCD_EN = 1;
delay_us(5);
LCD_EN = 0;
}
}
按照时序完成这个,基本上不会有太大问题了。剩下的就是初始化之类的,完整显示字符串和汉字实现见附件。
到这,基本的驱动算是完成了,但我想用液晶做人机界面及交互,必然会涉及到GUI,因此,底层必须实现画点函数。基于此,我完成了这个画点函数,下面就说一下我在实现这个函数过程中碰到的问题和解决办法。首先,由于LCD12864并没有画点指令,但好在还有一个256*64的DGRAM缓冲区,缓冲区和液晶位置映射图如下: GDRAM 2.jpg
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg
下图即为GDRAM图
GDRAM.jpg
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg
我初步的想法是,直接写入相关位置实现,但是这样做存在一个弊端,那就是在写入某行多个点的时候后边的点会覆盖前边的点,为了避免这种情况,我们需要在写入某点之前,将当前的位址的所有点读出,然后将数据或上当前某点,然后再写入该位址。即所谓的读->改->写。具体实现如下:
//画点函数
/*
GDRAM分布
256(16*16) X
|-----------------------------|-----------------------------------|
|0 | |
| 上半屏128*32 | 下半屏128*32 |
Y|32 | |
|-----------------------------|-----------------------------------|
水平x坐标每隔16个点才有一个位址,因此改变某个点需要知道该点位于这16个点的哪个位置
垂直y坐标在大于31时处于下半屏,小于31处于上半屏
*/
voidLCD_SetPoint(uint8_t x,uint8_t y)
{
uint16_t volatile readdata=0;
//判断处于哪行哪列
uint8_t x_pos,x_bit;//x_pos用来判断处于位址,x_bit用来判断处于位址中的位置
uint8_t y_pos,y_bit;//y_pos用来判断处于上半屏还是下半屏 y_bit用来判断位于哪行
y_pos=y/32; //0:上半屏;1:下半屏
y_bit=y%32; //得到具体行位置
x_pos=x/16;
x_bit=x%16;
WriteToLCD(LCD_CMD,LCD_EXTERN_SET);//扩展指令集
WriteToLCD(LCD_CMD,LCD_DRAW_OFF);//关掉绘图显示
WriteToLCD(LCD_CMD,0x80+y_bit);
WriteToLCD(LCD_CMD,0x80+8*y_pos+x_pos);
//数据线IO方向设定
GPIO_Set(GPIOE,PIN6,GPIO_MODE_IN,0,0,GPIO_PUPD_PD);
GPIO_Set(GPIOF,PIN6|PIN11,GPIO_MODE_IN,0,0,GPIO_PUPD_PD);
GPIO_Set(GPIOG,PIN6|PIN7|PIN8|PIN12|PIN15,GPIO_MODE_IN,0,0,GPIO_PUPD_PD);
delay_ms(1);
ReadByteFromLCD();//空读一次
readdata=ReadByteFromLCD();
readdata<<=8;
readdata|=ReadByteFromLCD();
WriteToLCD(LCD_CMD,0x80+y_bit);
WriteToLCD(LCD_CMD,0x80+8*y_pos+x_pos);
if(x_bit<8)
{
WriteToLCD(LCD_DATA,((uint8_t)(readdata>>8))|(0x01<<(7-x_bit)));
WriteToLCD(LCD_DATA,(uint8_t)readdata);
}else
{
WriteToLCD(LCD_DATA,(uint8_t)(readdata>>8));
WriteToLCD(LCD_DATA,(uint8_t)readdata|(0x01<<(15-x_bit)));
}
WriteToLCD(LCD_CMD,LCD_DRAW_ON);//开绘图显示
WriteToLCD(LCD_CMD,LCD_BASIC_SET);//回到基本指令集
}
voidLCD_Refresh_GRAM(void)
{
uint8_t i;uint16_t j;
WriteToLCD(LCD_CMD,LCD_EXTERN_SET);//扩展指令集
WriteToLCD(LCD_CMD,LCD_DRAW_OFF);//关掉绘图显示
for(i=0;i<32;i++)//遍历0-31行
{
WriteToLCD(LCD_CMD,0x80+i);//写入行地址
WriteToLCD(LCD_CMD,0x80); //写入列地址
for(j=0;j<32;j++)
{
WriteToLCD(LCD_DATA,LCD_GRAM[j]);
}
}
WriteToLCD(LCD_CMD,LCD_DRAW_ON);//开启绘图显示
WriteToLCD(LCD_CMD,LCD_BASIC_SET);//回到基本指令集
}
这样做理论上没有问题,但在实际操作的时候出现了一些错误,在读取位址的时候,将IO口设置为上拉输入,读取到的数据都是0xff,设置为下拉输入,则读到的数据都是0x00。
因此,该实现就卡死在了这里。后来也曾试过将IO口设置为开漏输出,加上拉。在读取数据前,向端口位写1,然后读回数据的方法,但是也不能解决问题。
经过一番的查找思路的过程,最终想到了用MCU内部缓冲区的办法。这种方案需要MCU内存不能太紧张,不过相对STM32F407ZGT6那192K的内存,这点开销毛毛雨了。于是乎在内存中开辟了一块缓冲区:
实现一个[32][32]RAM缓冲区
格式如下
//[0]0 1 2 3 ...31(字节)
//[1]0 1 2 3 ...31(字节)
//[2]0 1 2 3 ...31(字节)
//[3]0 1 2 3 ...31(字节)
[0..31]代表0-31行,0..31代表某行的所有字节(16*16/8=32)。
画点函数实现纯粹是操作uint8_t LCD_GRAM[32][32];
这个二维数组,在操作完毕后,更新缓冲区数据到GDRAM中即可。
//x:0-127
//y:0-63
//mode:1,画点
//mode:0,清空
void LCD_DrawPoint(uint8_t x,uint8_t y,uint8_t mode)
{
//判断处于哪行哪列
uint8_tx_pos,x_bit;//x_pos用来判断处于位址,x_bit用来判断处于位址中的位置
uint8_t y_pos,y_bit;//y_pos用来判断处于上半屏还是下半屏 y_bit用来判断位于哪行
// uint8_tx_pos_temp;
if((x>127)||(y>63)||(x<0)||(y<0))return;//去掉不合理参数
y_pos=y/32; //0:上半屏;1:下半屏
y_bit=y%32; //得到具体行位置
x_pos=x/16;
x_bit=x%16;
if(y_pos>0)//下半屏
{
if(mode)
{
if(x_bit<8)
{
LCD_GRAM[y_bit][x_pos*2+16]|=(1<<(7-x_bit));
LCD_GRAM[y_bit][x_pos*2+1+16]|=0x00;
}else
{
LCD_GRAM[y_bit][x_pos*2+16]|=0x00;
LCD_GRAM[y_bit][x_pos*2+1+16]|=(1<<(15-x_bit));
}
}
else
{
if(x_bit<8)
{
LCD_GRAM[y_bit][x_pos*2+16]&=~(1<<(7-x_bit));
LCD_GRAM[y_bit][x_pos*2+1+16]&=~0x00;
}else
{
LCD_GRAM[y_bit][x_pos*2+16]&=~0x00;
LCD_GRAM[y_bit][x_pos*2+1+16]&=~(1<<(15-x_bit));
}
}
}else//上半屏
{
if(mode)
{
if(x_bit<8)
{
LCD_GRAM[y_bit][x_pos*2]|=(1<<(7-x_bit));
LCD_GRAM[y_bit][x_pos*2+1]|=0x00;
}else
{
LCD_GRAM[y_bit][x_pos*2]|=0x00;
LCD_GRAM[y_bit][x_pos*2+1]|=(1<<(15-x_bit));
}
}
else
{
if(x_bit<8)
{
LCD_GRAM[y_bit][x_pos*2]&=~(1<<(7-x_bit));
LCD_GRAM[y_bit][x_pos*2+1]&=~0x00;
}else
{
LCD_GRAM[y_bit][x_pos*2]&=~0x00;
LCD_GRAM[y_bit][x_pos*2+1]&=~(1<<(15-x_bit));
}
}
}
// LCD_Refresh_GRAM();
}
这个函数是为最终的画点函数实现。调用此函数完毕后,再调用刷新缓冲区数据函数即可完成画点。
void LCD_Refresh_GRAM(void)
{
uint8_t i;uint16_t j;
WriteToLCD(LCD_CMD,LCD_EXTERN_SET);//扩展指令集
WriteToLCD(LCD_CMD,LCD_DRAW_OFF);//关掉绘图显示
for(i=0;i<32;i++)//遍历0-31行
{
WriteToLCD(LCD_CMD,0x80+i);//写入行地址
WriteToLCD(LCD_CMD,0x80); //写入列地址
for(j=0;j<32;j++)
{
WriteToLCD(LCD_DATA,LCD_GRAM[j]);
}
}
WriteToLCD(LCD_CMD,LCD_DRAW_ON);//开启绘图显示
WriteToLCD(LCD_CMD,LCD_BASIC_SET);//回到基本指令集
}
具体的代码请参看附件我的工程,下面附上一张调试完成的照片,仅作观赏之用。
2.png
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image006.jpg
本帖最后由 人民币的幻想 于 2015-4-16 09:17 编辑
作为一个菜逼,干货并没有多少。唯一会的就是水,所以回帖水分大。望见谅!
点赞  2015-4-14 13:29

驱动4*4矩阵键盘

矩阵键盘其实早就在51、STM32F1平台写过了,因此这个驱动写起来很快。只要了解了思路以后,程序so easy~。思路如下: YP20150416092953055.png
先扫描行,在扫描行的时候,列输出0,行上拉输入,一旦某行有按键被按下,那么按键会被导通,然后电平会被拉低,因此,读回数据中的0的位置即为被按下按键所在行的位置。
再扫描列,过程同扫描行,只不过列上拉输入,行输出0,然后回读数据,最终经过计算判断是哪个按键被按下,整个矩阵键盘扫描就完成了。
/***************************************************************************************
4*4矩阵键盘引脚连接
列1-4 PB6 PB7 PA0 PC7
行1-4 PC13 PD6 PD7 PE2
***************************************************************************************/
uint8_t KEYBoard_LRScan(void)
{
uint8_t key=0;
uint8_ttemp=0;
KEYBoard_LRInit_LScan();//扫描行
temp=(KEYBoard_GetInputVal()&0x0f);
if(temp==0x0e)
{
key=1;//第一行有键被按下
}else
if(temp==0x0d)
{
key=2;//第二行有键被按下
}else
if(temp==0x0b)
{
key=3;
}else
if(temp==0x07)
{
key=4;
}
elsekey=0;
KEYBoard_LRInit_RScan();//扫描列
temp=(KEYBoard_GetInputVal()&0xf0);
if((temp==0xe0)&&(key!=0))
{
key=(key-1)*4+1;
}else
if((temp==0xd0)&&(key!=0))
{
key=(key-1)*4+2;
}else
if((temp==0xb0)&&(key!=0))
{
key=(key-1)*4+3;
}else
if((temp==0x70)&&(key!=0))
{
key=(key-1)*4+4;
}
else key=0;
return key;
}
出于按键手感考虑,我换用了12*12*7的实体按键,自己焊接了矩阵键盘,
YP20150416093223787.png
YP20150416093234908.png
同样的,我也把碰到的问题写一下:
程序按如上思路写完以后,键盘不能正常工作,按键值只有3,7,11,15。也就是说按下任一行,只有第三列的按键响应,我查看了下代码,没有发现问题,然后看了一下第三列的原理图连接,发现探索者板子在这个脚上接了MPU6050的中断输出引脚,并且加了一个1K电阻,于是果断把第三列引脚换到PA0口,再次编译下载验证,没有问题。
QQ截图20150416093821.png
QQ截图20150416093806.png
去抖是通过连续采集到20次一样的按键值做的,
keyval_old=KEYBoard_LRScan();
if(keyval_old!=0)
{
keyval_new=KEYBoard_LRScan();
if((keyval_new!=0)&&(keyval_new==keyval_old))
{
key_counter++;
}
if(key_counter==20)
{
key_counter=0;
while(KEYBoard_LRScan());
printf("\r\nkeyval:%d\r\n",keyval_new);
}
}
QQ截图20150416094354.png
QQ截图20150416094631.png
本帖最后由 人民币的幻想 于 2015-4-16 10:32 编辑
作为一个菜逼,干货并没有多少。唯一会的就是水,所以回帖水分大。望见谅!
点赞  2015-4-14 13:29

【ADC采用】STM32F407ADC1双通道+DMA采用旋钮数值

毫无疑问,网络收音机需要频段音量调节,传统的按键调节起来感觉太难受,于是想到了旋钮的方式。于是动手做了一个旋钮模块,如下图。
YP20150423150907791.jpeg.JPG
YP20150416215208792.jpeg.JPG
为了固定方便,顺带打了2个定位孔,M3的螺丝刚刚好。言归正传,为了检测到旋钮的位置,需要将其变化转换为电压值,而完成这一转换的自然是ADC了,由于STM32F407已经内置了ADC,所以不需要再外置ADC芯片,本次实验使用的ADC1的通道4和通道5,即PA4和PA5,为了更加有效率的实现采样,我用来DMA方式实现,如下
/***************************************************************************************
旋钮输出模拟量,需要AD转换得到数字量
左:PA4 ADC12_IN4
右:PA5 ADC12_IN5
2015年4月16日完成
ADC1通道4和通道5使用DMA方式
DMA数据流0通道0是ADC1
ADC1_DR=ADC1_BASE+DR_OFFSET(0x4C)
ADC1_BASE=(APB2PERIPH_BASE + 0x2000)
APB2PERIPH_BASE=(PERIPH_BASE + 0x00010000)
PERIPH_BASE=((uint32_t)0x40000000)
计算ADC1_DR=0x4001204C
ADC3_DR=0x4001224C
***************************************************************************************/
__IO uint16_t ADCConvertedValue[2];
void Button_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDefADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_DMA2,ENABLE);//PA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//ADC1时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN;//模拟输入
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA,&GPIO_InitStructure);
DMA_InitStructure.DMA_Channel=DMA_Channel_0;
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)ADC1_DR_ADDRESS;
//ADCConvertedValue[0]存放ADC1通道4转换结果
DMA_InitStructure.DMA_Memory0BaseAddr=(uint32_t)&ADCConvertedValue;
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize=2;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority=DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0,&DMA_InitStructure);
DMA_Cmd(DMA2_Stream0,ENABLE);
//独立模式
ADC_CommonInitStructure.ADC_Mode=ADC_Mode_Independent;
//2个采样阶段延迟5个时钟周期
ADC_CommonInitStructure.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_5Cycles;
//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
ADC_CommonInitStructure.ADC_Prescaler=ADC_Prescaler_Div4;
//DMA失能
ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_InitStructure.ADC_Resolution= ADC_Resolution_12b;//12位模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//开启连续转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
ADC_InitStructure.ADC_NbrOfConversion = 2;//2个转换在规则序列中 也就是只转换规则序列1
ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化
ADC_DMACmd(ADC1,ENABLE);
ADC_RegularChannelConfig(ADC1,ADC_Channel_4, 1, ADC_SampleTime_144Cycles );
ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2, ADC_SampleTime_15Cycles);
//转换完成使能DMA请求
ADC_DMARequestAfterLastTransferCmd(ADC1,ENABLE);
ADC_Cmd(ADC1, ENABLE);//开启AD转换器
ADC_SoftwareStartConv(ADC1);
}
注意要点:DMA方向配置不要出错,还有保存数据的数组大小及类型
DMA_InitStructure.DMA_Memory0BaseAddr=(uint32_t)&ADCConvertedValue;
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize=2;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
剩下的就是,在主函数里初始化这个函数,然后从ADCConvertedValue[]数组取数了,可以看到,电压采集还是比较精确的。
旋钮数据变化.jpg
模块和主板连接好以后的样子如下图:
YP20150416215153412.jpeg(1).JPG
硬件平台全家福
YP20150416215811881.jpeg.JPG
本帖最后由 人民币的幻想 于 2015-4-23 15:28 编辑
  • YP20150416215229149.jpeg(1).JPG
  • YP20150416215446770.jpeg.JPG
  • YP20150416215726902.jpeg.JPG
作为一个菜逼,干货并没有多少。唯一会的就是水,所以回帖水分大。望见谅!
点赞  2015-4-14 13:30

【LCD显示文件长名】STM32F407平台移植znFAT读取SD卡汉字库实现中文长名文件显示

znFAT移植比较简单,首先将自己的SD卡初始化函数放到如下函数里
UINT8 znFAT_Device_Init(void)
{
UINT8 res=0,err=0;
ioctl.just_dev=0;
ioctl.just_sec=0;
//以下为各存储设备的初始化函数调用,请沿袭以下格式
res=SD_Init();
if(res) err|=0X01;
//res=Device1_Init();
//if(res) err|=0X02;
return err; //返回错误码,如果某一设备初始化失败,则err相应位为1
}
其次,按照如下格式将自己的SD卡读写扇区函数写好就行了。
UINT8 znFAT_Device_Read_Sector(UINT32addr,UINT8 *buffer)
{
if(buffer==znFAT_Buffer) //如果是针对znFAT内部缓冲区的操作
{
if(ioctl.just_dev==Dev_No //如果现在要读取的扇区与内部缓冲所对应的扇区(即最近一次操作的扇区)是同一扇区
&& (ioctl.just_sec==addr && 0!=ioctl.just_sec)) //则不再进行读取,直接返回
{
return 0;
}
else //否则,就将最近一次操作的扇区标记为当前扇区
{
ioctl.just_dev=Dev_No;
ioctl.just_sec=addr;
}
}
switch(Dev_No) //有多少个存储设备,就有多少个case分支
{
case 0:
while(SD_ReadDisk(buffer,addr,1));
break;
//case 1:
// while(SD2_Read_Sector(addr,buffer));
// break;
//case...
}
return 0;
}
UINT8znFAT_Device_Write_Sector(UINT32 addr,UINT8 *buffer)
{
if(buffer==znFAT_Buffer) //如果数据缓冲区是内部缓冲
{
ioctl.just_dev=Dev_No; //更新为当前设备号
ioctl.just_sec=addr; //更新为当前操作的扇区地址
}
switch(Dev_No)
{
case 0:
while(SD_WriteDisk(buffer,addr,1));
break;
//case 1:
// while(SD2_Write_Sector(addr,buffer));
// break;
//case...
}
return 0;
}
到此,znFAT文件系统就移植成功了,接下来这部分是长名显示需要做的步骤。
1. 首先判断文件是否存在长名
2. 存在长名,则将长名UNI码转换为UNI串,
void hexstring2string(struct FileInfo FileInfo,u8* dstbuf)
{
uint8_t i=0;uint16_t j=0;
while(FileInfo.longname!=0)
{
dstbuf[j]=text_hex2chr((FileInfo.longname>>12)&0x0F);
dstbuf[j+1]=text_hex2chr((FileInfo.longname>>8)&0x0F);
dstbuf[j+2]=text_hex2chr((FileInfo.longname>>4)&0x0F);
dstbuf[j+3]=text_hex2chr((FileInfo.longname>>0)&0x0F);
i++;j+=4;
}
dstbuf[j]=0;
}
3. 然后将UNI串转换为GBK码
//src:输入字符串
//dst:输出字符串(unigbk时为gbk内码,gbk2uni时为uni字符串)
//mode:0,uni2gbk
// 1,gbk2uni
void unigbk_exchange(u8*src,u8* dst,u8 mode)
{
u16 temp;
u8 buf[2];
if(mode)//gbk2uni
{
while(*src!='\0')
{
if(*src<0x81)//并不是汉字,因为汉字GBK码最小为0x8140
{
temp=(u16)unigbk((unsigned short)*src,1);
src++;
}else//为汉字 占用2个字节
{
buf[1]=*src++;
buf[0]=*src++;
//将某个汉字转换为了uni码
temp=(u16)unigbk((unsignedshort)*(u16*)buf,1);
}
*dst++=text_hex2chr((temp>>12)&0x0F);
*dst++=text_hex2chr((temp>>8)&0x0F);
*dst++=text_hex2chr((temp>>4)&0x0F);
*dst++=text_hex2chr((temp)&0x0F);
}
}
else//uni2gbk
{
while(*src!='\0')
{
buf[1]=text_chr2hex(*src++)*16;
buf[1]+=text_chr2hex(*src++);
buf[0]=text_chr2hex(*src++)*16;
buf[0]+=text_chr2hex(*src++);
temp=(u16)unigbk((unsignedshort)*(u16*)buf,0);
if(temp<0x80)
{
*dst=temp;dst++;
}else//需要交换一下高低字节,得到正确汉字
{
*(u16*)dst=swap16(temp);
dst+=2;
}
}
}
*dst=0;
}
4. 通过以上2个函数的使用,我们已经可以在串口助手看到长名数据了,但是我的目的是实现长名文件在LCD上的显示,这就需要花点功夫了,怎么做呢?我们经过unigbk_exchange函数得到了长名文件的GBK码,那么下一步就是根据GBK码得到其对应的汉字点阵,得到点阵后根据点阵数据在LCD上打点,然后刷新缓冲区就可以显示汉字了。PS:知道当初为什么非得把画点函数写好了吧~.~
//code 字符指针开始
//从字库中查找出字模
//code 字符串的开始地址,GBK码
//mat 数据存放地址 (size/8+((size%8)?1:0))*(size) bytes大小
//size:字体大小
void Get_HzMat(unsigned char *code,unsigned char *mat,u8 size)
{
unsigned char qh,ql;
unsigned char i;
unsigned long foffset;
u8csize=(size/8+((size%8)?1:0))*(size);//得到字体一个字符对应点阵集所占的字节数
qh=*code;
ql=*(++code);
if(qh<0x81||ql<0x40||ql==0xff||qh==0xff)//非 常用汉字
{
for(i=0;i
return; //结束访问
}
if(ql<0x7f)ql-=0x40;//注意!
else ql-=0x41;
qh-=0x81;
foffset=((unsignedlong)190*qh+ql)*csize; //得到字库中的字节偏移量
// printf("foffset:%ld",foffset);
switch(size)
{
case12:znFAT_ReadData(&fileinfo_f12,foffset,csize,mat);
break;
case16:znFAT_ReadData(&fileinfo_f16,foffset,csize,mat);
break;
case24:znFAT_ReadData(&fileinfo_f24,foffset,csize,mat);
break;
}
}
//显示一个指定大小的汉字
//x,y :汉字的坐标
//font:汉字GBK码
//size:字体大小
void Show_Font(unsigned char x,unsigned char y,u8 *font,u8 size)
{
u8 temp,t,t1;
u16 y0=y;
u8 dzk[72];
u8csize=(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
{
temp=dzk[t]; //得到点阵数据
for(t1=0;t1<8;t1++)
{
if(temp&0x80)
LCD_DrawPoint(x,y,1);
temp<<=1;
y++;
if((y-y0)==size)
{
y=y0;
x++;
break;
}
}
}
// LCD_Refresh_GRAM();
}
5. 单单显示一个字符当然不过瘾,需要在LCD上实现字符串的显示,基于此,我编写了这个函数,12、16、24字体自动换行显示。
//在指定位置开始显示一个字符串
//(x,y):起始坐标
//str :字符串
//size :字体大小
void Show_Str(unsigned char x,unsigned char y,u8*str,u8 size)
{
uint8_t i=0;
uint8_t isHZ=0;
if(x>127||y>63)return;
while(str!='\0')//判断数据是否结束
{
if(!isHZ)
{
if(str>0x80)isHZ=1;//中文
else //字符
{
if(x>127-11)
{
x=0;
if(size!=24)
y+=16;
else
y+=24;
}
if(size!=24)
{
if(y>63-11)
{
y=0;
LCD_Clear_DGRAM();
}
}else
{
if(y>63-23)
{
y=0;
LCD_Clear_DGRAM();
}
}
LCD_ShowChar(x,y,*(str+i),size,1);
x+=size;
i++;
}
}else//中文
{
isHZ=0;
if(x>127-11)
{
x=0;
if(size!=24)
y+=16;
else
y+=24;
}
if(size!=24)
{
if(y>63-11)
{
y=0;
LCD_Clear_DGRAM();
}
}else
{
if(y>63-23)
{
y=0;
LCD_Clear_DGRAM();
}
}
Show_Font(x,y,str+i,size);
x+=size;
i+=2;
}
}
}
到此,整个长名显示的步骤都完成了,具体实现请看我的代码工程。
长名测试1.jpg
长名测试2.jpg
YP20150417212610627.jpeg.JPG
本帖最后由 人民币的幻想 于 2015-4-23 16:07 编辑
作为一个菜逼,干货并没有多少。唯一会的就是水,所以回帖水分大。望见谅!
点赞  2015-4-14 13:31

6楼 nmg 

快5月了开始做毕设,哈哈
楼主抓紧啊
点赞  2015-4-14 13:35
坐等楼主更新
点赞  2015-4-14 13:55
楼主很强大。
点赞  2015-4-14 15:50
为何不考虑wifi? 拖一根网线的收音机不好玩啊
默认摸鱼,再摸鱼。2022、9、28
点赞  2015-4-14 16:05
跟着楼主学习了!rt-thread有个项目做这个的。
点赞  2015-4-14 16:17
引用: flyword 发表于 2015-4-14 16:17
跟着楼主学习了!rt-thread有个项目做这个的。

对,我的思路就是他们的项目。但是我换了平台和方案实现。
作为一个菜逼,干货并没有多少。唯一会的就是水,所以回帖水分大。望见谅!
点赞  2015-4-14 16:32
引用: freebsder 发表于 2015-4-14 16:05
为何不考虑wifi? 拖一根网线的收音机不好玩啊

兜里没米了,搞不起sdio wifi了。
作为一个菜逼,干货并没有多少。唯一会的就是水,所以回帖水分大。望见谅!
点赞  2015-4-14 16:35
引用: 人民币的幻想 发表于 2015-4-14 16:35
兜里没米了,搞不起sdio wifi了。

搞个wifi的带劲啊,哈哈。强烈建议啊!!
点赞  2015-4-15 09:06
引用: 人民币的幻想 发表于 2015-4-14 16:35
兜里没米了,搞不起sdio wifi了。

这wifi多少钱?
加油!在电子行业默默贡献自己的力量!:)
点赞  2015-4-15 09:41
引用: soso 发表于 2015-4-15 09:41
这wifi多少钱?

100大洋左右
作为一个菜逼,干货并没有多少。唯一会的就是水,所以回帖水分大。望见谅!
点赞  2015-4-15 10:32

做吧  完全分享完 我们给报销  可能还有额外奖励哈
加油!在电子行业默默贡献自己的力量!:)
点赞  2015-4-15 10:34
引用: soso 发表于 2015-4-15 10:34
做吧  完全分享完 我们给报销  可能还有额外奖励哈


还准备把庆科的wifi支持给楼主的


毕业季的时候搜罗支持一些这种开源毕设

听上去很牛x啊
So TM what......?
点赞  2015-4-15 14:51
引用: ljj3166 发表于 2015-4-15 14:51
还准备把庆科的wifi支持给楼主的


毕业季的时候搜罗支持一些这种开源毕设

听上去很牛x啊

给力的版主,要不你发起活动,我们来支持一些奖品吧。
加油!在电子行业默默贡献自己的力量!:)
点赞  2015-4-15 15:00
引用: soso 发表于 2015-4-15 15:00
给力的版主,要不你发起活动,我们来支持一些奖品吧。

今年估计是赶不上了

貌似本月底,下月初就开始陆陆续续答辩了



话说回来,有潜力、有意思的开源毕设,真的可以考虑支持一下

毕竟是非做不可的
So TM what......?
点赞  2015-4-15 15:08
嗯 是的  嘿嘿 那下次咱提早筹备   嘿嘿  
加油!在电子行业默默贡献自己的力量!:)
点赞  2015-4-15 15:13
12下一页
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复