普中开发板上的是4X4的矩阵按键,代码编写如下。写的不是很优雅,但也算好用。
u8 matrix_key(void)
{
u8 key_num=0;
P13=0;P12=1;P11=1;P10=1;
if(P17==0)
{
key_num=1;
}else if(P16==0)
{
key_num=2;
}else if(P15==0)
{
key_num=3;
}else if(P14==0)
{
key_num=4;
}
P13=1;P12=0;P11=1;P10=1;
if(P17==0)
{
key_num=5;
}else if(P16==0)
{
key_num=6;
}else if(P15==0)
{
key_num=7;
}else if(P14==0)
{
key_num=8;
}
P13=1;P12=1;P11=0;P10=1;
if(P17==0)
{
key_num=9;
}else if(P16==0)
{
key_num=10;
}else if(P15==0)
{
key_num=11;
}else if(P14==0)
{
key_num=12;
}
P13=1;P12=1;P11=1;P10=0;
if(P17==0)
{
key_num=13;
}else if(P16==0)
{
key_num=14;
}else if(P15==0)
{
key_num=15;
}else if(P14==0)
{
key_num=16;
}
return key_num;
}
第十二期(第12集《复位系统》)
这集视频比较全面地介绍了复位系统。注意到:
89C52的复位脚是高电平复位,而AI8051U是低电平复位,所以普中开发板上的复位按键不起作用,但好在擎天柱上的复位按键是可以用的。
另外,USB复位代码很重要,不然按复位之后,串口通讯将失效,printf打印不出东西来!
//复位USB 很重要
P3M0 &=~0x03;
P3M1 |=0x03;
USBCON=0x00;
USBCLK=0x00;
IRC48MCR=0x00;
delay_ms(10);
第十三期(第13集《外部中断》)
本来外部中断的难点是不记得哪些引脚支持,以及对应的中断向量号。不过幸好有ISP工具自动生成代码,所以用起来还比较顺。
第十四期(第14集《IO中断》)
由于Keil的中断向量号最高只能支持到31,所以需要下载插件来进一步拓展,或者通过保留的13号中断来路由。由于后者需要用到汇编语言(完全不懂),所以貌似还是前者更方便。
;P3的13号中断
;CSEG AT 0143H
;JMP P3INT_SR
;P3INT_SR:
;JMP 006BH
;END
第十五期(第15集《定时器做计数器》)
定时器如果切换为计数器,就可以利用特定引脚计量输入的脉冲数,实现编码器测速等功能。
#define COUNTER 10
void Timer1_Init(void) //计数器
{
AUXR |= 0x40; //定时器时钟1T模式
TMOD &= 0x0F; //设置定时器模式为计数器
TMOD |= 0x40;
TL1 = (65536-COUNTER); //设置定时初始值
TH1 = (65536-COUNTER)>>8; //设置定时初始值
TF1 = 0; //清除TF1标志
P3PU |=0x20; //打开P3.5上拉电阻
TR1 = 1; //定时器1开始计时
ET1 = 1; //使能定时器1中断
}
第十六期(第16集《DS18B20测温》)
这节课温习了一下单总线协议的写法。视频讲得很细,挺好的。
#include "18B20.h"
#include "io.h"
u32 DS18B20_Temp=0;
u8 minus=0;
void Delay480us(void) //@40.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
_nop_();
i = 4798UL;
while (i) i--;
}
void Delay60us(void) //@40.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
_nop_();
i = 598UL;
while (i) i--;
}
void Delay1us(void) //@40.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
_nop_();
i = 8UL;
while (i) i--;
}
void DS18B20_reset(void)
{
u8 flag=1;
while(flag)
{
DQ=0;
Delay480us();
DQ=1;
Delay60us();
flag=DQ;
Delay480us();
}
}
void DS18B20_write_0(void)
{
DQ=0;
Delay60us();
DQ=1;
Delay1us();
Delay1us();
}
void DS18B20_write_1(void)
{
DQ=0;
Delay1us();
Delay1us();
DQ=1;
Delay60us();
}
bit DS18B20_read(void)
{
bit b=0;
DQ=0;
Delay1us();
Delay1us();
DQ=1;
Delay1us();
Delay1us();
b=DQ;
Delay60us();
return b;
}
void DS18B20_write_byte(u8 dat)
{
u8 i;
for(i=0;i<8;i++)
{
if((dat>>i) & 0x01)
DS18B20_write_1();
else
DS18B20_write_0();
}
}
u8 DS18B20_read_byte(void)
{
u8 dat=0;
u8 i;
for(i=0;i<8;i++)
{
dat >>= 1;
if(DS18B20_read())
dat |= 0x80;
}
return dat;
}
void DS18B20_ReadTemp(void)
{
u8 datH=0, datL=0;
u16 temp=0;
/* 发送检测命令 */
DS18B20_reset();
DS18B20_write_byte(0xCC); //跳过ROM命令
DS18B20_write_byte(0x44); //开始转化
while(!DQ);
/* 发送读取命令 */
DS18B20_reset();
DS18B20_write_byte(0xCC); //跳过ROM命令
DS18B20_write_byte(0xBE); //开始读取
datL=DS18B20_read_byte();
datH=DS18B20_read_byte();
if(datH & 0x80)
{
minus=1;
temp=((u16)datH<<8)|(u16)datL;
temp =~temp+1; //负数取补码
DS18B20_Temp=(u32)temp*625; //放大1万倍
}
else
{
minus=0;
temp=((u16)datH<<8)|(u16)datL;
DS18B20_Temp=(u32)temp*625; //放大1万倍
}
}
void DS18B20_ShowTemp(u8 digit)
{
u8 i;
u32 new_temp;
if(digit>4) digit=4;//最多4为小数
new_temp=DS18B20_Temp/(u32)pow(10,4-digit);
for(i=0;i<digit+2;i++)
{
if(i==digit)
digit_show((u8)(new_temp/(u32)pow(10,i)%10),(u8)(i+1),1);
else
digit_show((u8)(new_temp/(u32)pow(10,i)%10),(u8)(i+1),0);
}
}
第十七期(第17集《串口的简单应用》)
第十八期(第18集《串口高级应用》)
利用串口超时中断接收不定长数据非常实用。不过因为计算合适的超时时长还是比较复杂,所以干脆可以把时长设长一点。我一开始用AiCube自动配置的10个串口波特率时长会触发两次超时中断,延长到255个就好了。
//<<AICUBE_USER_HEADER_REMARK_BEGIN>>
////////////////////////////////////////
// 在此添加用户文件头说明信息
// 文件名称: uart.c
// 文件描述:
// 文件版本: V1.0
// 修改记录:
// 1. (2025-04-30) 创建文件
////////////////////////////////////////
//<<AICUBE_USER_HEADER_REMARK_END>>
#include "config.h"
//<<AICUBE_USER_INCLUDE_BEGIN>>
// 在此添加用户头文件包含
//<<AICUBE_USER_INCLUDE_END>>
//<<AICUBE_USER_GLOBAL_DEFINE_BEGIN>>
u8 Rec_Dat;
u8 Rec_Num=0;
bit B_TX2_Busy=0;
bit B_TX2_OK=0;
//<<AICUBE_USER_GLOBAL_DEFINE_END>>
////////////////////////////////////////
// 串口2中断服务程序
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void UART2_ISR(void) interrupt UART2_VECTOR
{
//<<AICUBE_USER_UART2_ISR_CODE1_BEGIN>>
// 在此添加中断函数用户代码
if (UART2_CheckTxFlag()) //判断串口发送中断
{
UART2_ClearTxFlag(); //清除串口发送中断标志
B_TX2_Busy=0;
}
if (UART2_CheckRxFlag()) //判断串口接收中断
{
UART2_ClearRxFlag(); //清除串口接收中断标志
UR2TOCR = 0xe0;
Rec_Dat=S2BUF;
}
if(UR2TOSR & 0x01)
{
B_TX2_OK = 1;
UR2TOSR = 0;
UR2TOCR = 0x00;
}
//<<AICUBE_USER_UART2_ISR_CODE1_END>>
}
////////////////////////////////////////
// 串口2初始化函数
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void UART2_Init(void)
{
#ifdef BAUDRATE
#undef BAUDRATE
#endif
#define BAUDRATE (115200)
#define T2_RELOAD (65536 - (SYSCLK / BAUDRATE + 2) / 4)
UART2_SwitchP4243(); //设置串口数据端口: RxD2 (P4.2), TxD2 (P4.3)
//UART2_Timer2BRT(); //串口2固定选择定时器2作为波特率发生器
TIMER2_TimerMode(); //设置定时器2为定时模式
TIMER2_1TMode(); //设置定时器2为1T模式
TIMER2_SetPrescale(0); //设置定时器2的8位预分频
TIMER2_SetReload16(T2_RELOAD); //设置定时器2的16位重载值
TIMER2_Run(); //定时器2开始运行
UART2_EnableRx(); //使能串口2接收数据
UART2_Mode3(); //设置串口2为模式3 (9位数据可变波特率)
UART2_OddParity(); //串口2硬件自动奇校验
UART2_SetIntPriority(0); //设置中断为最低优先级
UART2_EnableInt(); //使能串口2中断
UART2_TimeoutScale_BRT(); //设置串口波特率为接收超时时间基准
UART2_SetTimeoutInterval(0x0000ff); //设置接收超时时间长度
UART2_EnableTimeoutInt(); //使能串口接收超时中断
UART2_EnableTimeout(); //使能串口接收超时功能
//<<AICUBE_USER_UART2_INITIAL_BEGIN>>
// 在此添加用户初始化代码
//<<AICUBE_USER_UART2_INITIAL_END>>
}
//<<AICUBE_USER_FUNCTION_IMPLEMENT_BEGIN>>
void uart_receive(void)
{
if(B_TX2_OK)
{
B_TX2_OK=0;
uart_send("receive finish\r\n");
}
if(Rec_Num>=6)
{
if(Rec_Dat=='\n' && Rec_Dat=='\r')
{
if(Rec_Dat=='o' && Rec_Dat=='p'&& Rec_Dat=='e' && Rec_Dat=='n')
{
P00=0;
uart_send("open\r\n");
}
if(Rec_Dat=='c' && Rec_Dat=='l' && Rec_Dat=='o'&& Rec_Dat=='s' && Rec_Dat=='e')
{
P00=1;
uart_send("closed\r\n");
}
Rec_Num=0;
}
}
}
void uart_send(u8 *dat)
{
for(;*dat !=0; dat++)
{
S2BUF=*dat;
B_TX2_Busy=1;
while(B_TX2_Busy);
}
}
//<<AICUBE_USER_FUNCTION_IMPLEMENT_END>>
第十九期(第19集《ADC》)
让人兴奋的是:
1.申请的Ai8951U实验箱收到了,我的设备又一次更新了
2.ISP6.95N更新了AiCube,现在可以在用户自定义区编写代码且刷新工程后不会被擦除了,越来越接近CubeMX了。(不过好像还有bug,例如刷新工程后,原来添加到keil的自定义文件会被移除,需要重新添加到目录中)
这个五一可以好好研究一下了。
今天学习了ADC章节。发现自己写的ADC按键代码有问题,虽然按下按键后能够正常显示按键值,但是一旦松开,按键值便归零。理解是之前的值无法存在全局/静态变量中。研究了一晚上发现,如果将ADC转换函数加入到定时器中断,使其间隔100ms运行一次就好了(至少要间隔50ms)。猜想是ADC转换需要一定的时间,如果两次转换函数间隔时间太近,会对全局变量存储结果产生影响,导致其无法存储上一次按下的值。(但仅仅是猜想,不知道准确的原因如何描述)
第二十期(第20集《ADC_NTC测温》)
之前普中A2的板子因为用的是89C52,不带ADC,所以开发板上是通过一颗ET2046芯片实现ADC功能的。这次给板子插上了擎天柱,顺便也向客服问到了普中上板载的热敏电阻和光敏电阻型号,在普中和实验箱上同时做了实验。
普中上的电阻参数记录如下:
1.NTC:MF52/10K/3950/1%
2.光敏:5549/5MM/50K-100K
void show_temperature(void)
{
u16 adc_val=Adc_read(0x00);
double ntc_val;
double temperature;
u16 tempX10;
ntc_val=4096.0*10000/adc_val-10000;
temperature=1/(log(ntc_val/10000)/3950+1/(273.15+25))-273.15;
tempX10=(u16)(temperature*10);
digit_show((u8)(tempX10/100%10),3,0);
delay_ms(5);
digit_show((u8)(tempX10/10%10),2,1);
delay_ms(5);
digit_show((u8)(tempX10%10),1,0);
delay_ms(5);
}
第二十一期(第21集《Flash模拟EEPROM》)
这集貌似跟着移植代码就好了,比较顺利。
#include "eeprom.h"
//========================================================================
// 函数: void DisableEEPROM(void)
// 描述: 禁止EEPROM.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void DisableEEPROM(void) //禁止访问EEPROM
{
IAP_CONTR = 0; //关闭 IAP 功能
IAP_CMD = 0; //清除命令寄存器
IAP_TRIG = 0; //清除触发寄存器
IAP_ADDRE = 0xff; //将地址设置到非 IAP 区域
IAP_ADDRH = 0xff; //将地址设置到非 IAP 区域
IAP_ADDRL = 0xff;
}
//========================================================================
// 函数: void EEPROM_Trig(void)
// 描述: 触发EEPROM操作.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_Trig(void)
{
F0 = EA; //保存全局中断
EA = 0; //禁止中断, 避免触发命令无效
IAP_TRIG = 0x5A;
IAP_TRIG = 0xA5; //先送5AH,再送A5H到IAP触发寄存器,每次都需要如此
//送完A5H后,IAP命令立即被触发启动
//CPU等待IAP完成后,才会继续执行程序。
_nop_(); //多级流水线的指令系统,触发命令后建议加4个NOP,保证IAP_DATA的数据完成准备
_nop_();
_nop_();
_nop_();
EA = F0; //恢复全局中断
}
//========================================================================
// 函数: void EEPROM_SectorErase(u32 EE_address)
// 描述: 擦除一个扇区.
// 参数: EE_address:要擦除的EEPROM的扇区中的一个字节地址.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_SectorErase(u32 EE_address)
{
IAP_ENABLE(); //设置等待时间,允许IAP操作,送一次就够
IAP_ERASE(); //宏调用, 送扇区擦除命令,命令不需改变时,不需重新送命令
//只有扇区擦除,没有字节擦除,512字节/扇区。
//扇区中任意一个字节地址都是扇区地址。
IAP_ADDRE = (u8)(EE_address >> 16); //送扇区地址高字节(地址需要改变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8);//送扇区地址中字节(地址需要改变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送扇区地址低字节(地址需要改变时才需重新送地址)
EEPROM_Trig(); //触发EEPROM操作
DisableEEPROM(); //禁止EEPROM操作
}
//========================================================================
// 函数: void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 lenth)
// 描述: 读N个字节函数.
// 参数: EE_address:要读出的EEPROM的首地址.
// DataAddress: 要读出数据的指针.
// length: 要读出的长度
// 返回: 0: 写入正确.1: 写入长度为0错误.2: 写入数据错误.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 length)
{
IAP_ENABLE(); //设置等待时间,允许IAP操作,送一次就够
IAP_READ(); //送字节读命令,命令不需改变时,不需重新送命令
do
{
IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8);//送地址中字节(地址需要改变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送地址低字节(地址需要改变时才需重新送地址)
EEPROM_Trig(); //触发EEPROM操作
*DataAddress = IAP_DATA; //读出的数据送往
EE_address++;
DataAddress++;
}while(--length);
DisableEEPROM();
}
//========================================================================
// 函数: u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length)
// 描述: 写N个字节函数.
// 参数: EE_address:要写入的EEPROM的首地址.
// DataAddress: 要写入数据的指针.
// length: 要写入的长度
// 返回: 0: 写入正确.1: 写入长度为0错误.2: 写入数据错误.
// 版本: V1.0, 2014-6-30
//========================================================================
u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length)
{
u8i;
u16 j;
u8*p;
if(length == 0) return 1; //长度为0错误
IAP_ENABLE(); //设置等待时间,允许IAP操作,送一次就够
i = length;
j = EE_address;
p = DataAddress;
IAP_WRITE(); //宏调用, 送字节写命令
do
{
IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8);//送地址中字节(地址需要改变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送地址低字节(地址需要改变时才需重新送地址)
IAP_DATA= *DataAddress; //送数据到IAP_DATA,只有数据改变时才需重新送
EEPROM_Trig(); //触发EEPROM操作
EE_address++; //下一个地址
DataAddress++; //下一个数据
}while(--length); //直到结束
EE_address = j;
length = i;
DataAddress = p;
i = 0;
IAP_READ(); //读N个字节并比较
do
{
IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8);//送地址中字节(地址需要改变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送地址低字节(地址需要改变时才需重新送地址)
EEPROM_Trig(); //触发EEPROM操作
if(*DataAddress != IAP_DATA) //读出的数据与源数据比较
{
i = 2;
break;
}
EE_address++;
DataAddress++;
}while(--length);
DisableEEPROM();
return i;
}
#define START_ADDR 0
#define PWD 0xfe
u8 SYS_RUNTIME=0;
void EEPROM_Init(void)
{
u8 dat; //0:操作码 1:开机次数 2:校验码
EEPROM_read_n(START_ADDR,dat,3);
if(dat!=PWD)
{
EEPROM_SectorErase(START_ADDR);
dat=PWD;
dat=0;
dat=dat+dat;
EEPROM_write_n(START_ADDR,dat,3);
SYS_RUNTIME=0;
}
else{
if(dat==(u8)(dat+dat)){
SYS_RUNTIME=dat;
SYS_RUNTIME++;
EEPROM_SectorErase(START_ADDR);
dat=PWD;
dat=SYS_RUNTIME;
dat=dat+dat;
EEPROM_write_n(START_ADDR,dat,3);
}
}
}
页:
1
[2]