外设相关应用
📎 原始文档:https://www.kdocs.cn/l/ct2UBfl5gBos 外设相关应用
- GPIO
状态说明
- 方向:有输入,有输出,无开漏。
- 上下拉:所有IO都有上下拉, 电阻固定, 但不同芯片, 或不同引脚的阻值是有差异的, 具体看芯片的规格书。输入状态才有上下拉。
- 水平:输出高(电压接近于VDDIO电压),输出低(相当于地,灌电流形式)
- 能力:普通输出、强输出、超强输出,3种输出能力。超强输出只有极少IO有此能力。
- 状态:数字态,模拟态,2种状态。输入状态下AD检测才用模拟态。
- 读取:输入数字态,可以读取IO的电压高低。 IO的承受电压不能高于VDDIO,否则会有倒灌,从而影响其他IO的检测
接口说明
第一个参数都是IO的宏定义, 如:IO_PORTA_00,指 PA0
- gpio_set_direction() 设置IO的方向,第二个参数:1输入,0输出
- gpio_set_pull_up() 设置上拉电阻的使能,第二个参数:1开启,0关闭
- gpio_set_pull_down() 设置下拉电阻的使能,第二个参数:1开启,0关闭
- gpio_set_output_value() 设置输出电压水平,第二个参数:1高,0低
- gpio_set_die(), gpio_set_dieh() 设置IO的状态,第二个参数:1数字态,0模拟态
- gpio_set_hd0(), gpio_set_hd() 设置IO的能力,第二个参数:1强输出,0普通输出
- gpio_read() 输入数字态下读取电压水平,返回值:1是高,0是低
特殊IO
- usb_iomode(),usb1_iomode() 参数1:普通IO,0:是USB口
- IO_PORTP_00 原理图标注的PP0,用法与普通IO一致
- IO_PORT_PR_00 ... IO_PORT_PR_04 原理图标注PR0 - PR4,属于rtc模块引脚,不属于总线IO,有特殊的函数接口如下: AC695N
第一: RTCVDD 要外部供电: 可以使用 VDDIO 供电
第二: RTC 晶振使能要关掉: p33_tx_1byte(R3_OSL_CON,0);
示范代码如果要再开发板测硬件上需要做以下修改
- P00 原理图标注的P00,属于低功耗模块IO,不属于总线IO,只能做输出,有特殊的函数接口
寄存器说明
typedef struct {
__RW __u32 OUT; //gpio_set_output_value
__RO __u32 IN; //gpio_read
__RW __u32 DIR; //gpio_set_direction
__RW __u32 DIE; //gpio_set_die
__RW __u32 PU; //gpio_set_pull_up
__RW __u32 PD; //gpio_set_pull_down
__RW __u32 HD0; //gpio_set_hd0
__RW __u32 HD; //gpio_set_hd
__RW __u32 DIEH; //gpio_set_dieh
} JL_PORT_FLASH_TypeDef;
gpio设置范例
设置输出高低
u32 gpio = IO_PORTA_00;//指定IO
gpio_set_pull_down(gpio, 0);
gpio_set_pull_up(gpio, 0);
gpio_set_die(gpio, 1);
gpio_set_hd(gpio, 0);//看需求是否需要开启强推,会导致芯片功耗大
gpio_set_hd0(gpio, 0);
gpio_set_direction(gpio, 0);
gpio_set_output_value(gpio, 1); //1高0低
设置输入读取IO高低
u32 gpio = IO_PORTA_00;//指定IO
gpio_set_pull_down(gpio, 0);//看需求是否需要开内部下拉
gpio_set_pull_up(gpio, 0);//看需求是否需要开内部上拉
gpio_set_die(gpio, 1);
gpio_set_direction(gpio, 1);
delay(100);//设置方向寄存器后,不能立马读电压
int level = gpio_read(gpio);
设置输入模拟检测电压
u32 gpio = IO_PORTA_00;//指定IO
gpio_set_pull_down(gpio, 0);//看需求是否需要开内部下拉
gpio_set_pull_up(gpio, 0);//看需求是否需要开内部上拉
gpio_set_die(gpio, 0);
gpio_set_direction(gpio, 1);
可视化SDK 同时设置上下拉
u32 port = PORTA;//指定IO
u32 pin = PORT_PIN_2;
gpio_hw_set_direction(port, pin, 1);//0:out, 1:in
gpio_hw_set_die(port, pin, 0);
gpio_hw_set_dieh(port, pin, 0);
gpio_hw_set_pull_up(port, pin, GPIO_PULLUP_10K);
gpio_hw_set_pull_down(port, pin, GPIO_PULLDOWN_10K);
gpio_hw_direction_output(port,pin, 1);
打印监控IO状态
JL_PORT_FLASH_TypeDef *PORT = JL_PORTA; //指定组口
u8 bit = 0; //指定位口,组合起来,即 PA0 == IO_PORTA_00
printf("port:DIR-%d,PU-%d,PD-%d,DIE-%d,DIEH-%d,HD0-%d,HD-%d,OUT-%d,IN-%d\n",
(PORT->DIR &BIT(bit))?1:0,
(PORT->PU &BIT(bit))?1:0,
(PORT->PD &BIT(bit))?1:0,
(PORT->DIE &BIT(bit))?1:0,
(PORT->DIEH&BIT(bit))?1:0,
(PORT->HD0 &BIT(bit))?1:0,
(PORT->HD &BIT(bit))?1:0,
(PORT->OUT &BIT(bit))?1:0,
(PORT->IN &BIT(bit))?1:0
);
- 唤醒源获取及唤醒IO设置 要获取唤醒的IO口,唤醒口都要选ROM后的IO!!!!!!
- AC695 IO口唤醒源获取
u8 get_wakeup_source(void); //返回值 为对应唤醒源的index
u8 get_wakeup_pnd(void); //返回值 为对应唤醒口
- AC700X IO口唤醒源获取 通过获取寄存器P3_WKUP_PND0 值,得到唤醒源。值的对应关系和调用如下:
获取P3_WKUP_PND0 值:
P3_WKUP_PND0 值对应的唤醒源(例子):

- BR25(AC696,AC6082)接口添加
u8 wkup_pnd = 0;
u8 get_wakeup_pnd(void)
{
return wkup_pnd;
}
//返回值: 0x00 | BIT(x)//x唤醒的IO口编号
在此处添加图片两行代码:
- AC701N 唤醒源获取
- 复位源 可以通过u8 is_reset_source(enum RST_REASON index); 判断复位源进行判断,调试可以通过打印 wakeup_source_value_dump(); 打印出唤醒源的值。
u8 is_reset_source(enum RST_REASON index); //判断复位源
reset_source_dump(); //打印复位源源
//demo
//更多唤醒源查看 include_lib\driver\cpu\br28\asm\power\power_reset.h 定义
is_reset_source(MSYS_POWER_RETURN); //返回1 则代表是 上电复位
- powerdown 唤醒IO
AC701N_soundbox_release 音箱SDK 唤醒IO会有一个回调函数:可以根据回调函数对IO口进行判断
static void port_wakeup_callback(u8 index, u8 gpio)
{
log_info("%s:%d,%d",__FUNCTION__,index,gpio);
switch (index) {
#if (TCFG_TEST_BOX_ENABLE || TCFG_CHARGESTORE_ENABLE || TCFG_ANC_BOX_ENABLE)
case 2:
extern void chargestore_ldo5v_fall_deal(void);
chargestore_ldo5v_fall_deal();
break;
#endif
}
}
- 软关机唤醒IO
可以通过u8 is_wakeup_source(enum WAKEUP_REASON index); 对唤醒源进行判断,调试可以通过打印 wakeup_source_value_dump(); 打印出唤醒源的值
u8 is_wakeup_source(enum WAKEUP_REASON index); //判断唤醒源
wakeup_source_value_dump(); //打印唤醒源
//demo
//更多唤醒源查看 include_lib\driver\cpu\br28\asm\power\power_wakeup.h 定义
is_wakeup_source(PWR_WK_REASON_EDGE_INDEX1); //返回1 则代表是 wk_param.port[1] 口唤醒
is_wakeup_source(PWR_WK_REASON_EDGE_INDEX2); //返回1 则代表是 wk_param.port[2] 口唤醒
- SDK打印的复位源含义
AC695X 为例:
u8 power_reset_source_dump(void);
VDDIO POR:上电
VDDIO LVD:LVD复位, 电压低于LVD设定电压, 芯片复位
WDT:看门狗
VCM:VCOM复位
PPINR:长按reset IO(按键)复位
SYS RESET:主系统复位了p33
SOFT RESET:软件复位
Vcom做瞬间复位
VCM_DET_EN(1);
//P33_CON_SET(P3_ANA_CON2, 3, 1, en); //VCM detect enable bit
打印测试:


备注:适用于AC696X\AC695X\AC697X\AC698X\AC700X 等
- 注册多个IO口唤醒
- AC700X 注册多IO口做唤醒功能(BR23,BR25同样适用): demo:

- AC700X 注册多IO口做唤醒功能(BR23,BR25同样适用): demo:

在关机设置IO口状态的函数中,要做好对应保护,不能改其状态:
- TIMER定时器
- 16位定时器寄存器说明 此处以695用户手册举例,详细可见用户手册
Timer16 是一个集合了定时/计数/捕获功能于一体的多动能 16 位定时器,的驱动源可以选择
片内时钟或片外信号。


2. 定时器应用
- 定时器中断使用(此处以696x led7_timer.c文件举例)
static void timer2_isr()
{
//local_irq_disable(); //需要极准确的定时效果时开启
TIMER_CON |= BIT(14); //清中断标志位
//用户自定义代码
//......
//local_irq_enable(); //需要极准确的定时效果时开启
}
int led7_timer_init()
{
u32 prd_cnt;
u8 index;
printf("------------%s :%d", __func__, __LINE__);
for (index = 0; index < (sizeof(timer_div) / sizeof(timer_div[0])); index++) {
prd_cnt = TIMER_UNIT_MS * (APP_TIMER_CLK / 1000) / timer_div[index];
if (prd_cnt > MIN_TIME_CNT && prd_cnt < MAX_TIME_CNT) {
break;
}
}
__this->index = index;
__this->prd = prd_cnt;
TIMER_CNT = 0;
TIMER_PRD = prd_cnt; //1ms
request_irq(TIMER_VETOR, 6, timer2_isr, 0); //最高优先级7,需要极准确的定时效果时开启
TIMER_CON = (index << 4) | BIT(0) | BIT(3);//修改定时器工作模式和驱动源
printf("PRD : 0x%x / %d", TIMER_PRD, clk_get("timer"));
return 0;
}
- PWM应用 PWM详细介绍和具体应用见外设相关应用.otl文档【PWM】项
3. 定时器捕捉功能
以下为基于696x程序定时器捕捉demo,也可以参考 irkey按键初始化流程
//基于696程序 存在一些IO口无法做此功能,先测试,再画板 !!!!!!
#define TIMER5 0x7 //见irflt.h
#define CATCH_TIMER TIMER5
#define CATCH_IRQ_TIME_IDX IRQ_TIME5_IDX
#define CATCH_TIME_REG JL_TIMER5
#define CATCH_GPIO IO_PORTB_06
int num = 0;
void timer_ms()
{
static int flag = 0;
if(flag){
gpio_set_pull_down(IO_PORTA_02,0);
gpio_set_pull_up(IO_PORTA_02,0);
gpio_set_direction(IO_PORTA_02,1);
flag = 0;
}
else{
gpio_set_pull_down(IO_PORTA_02,0);
gpio_set_pull_up(IO_PORTA_02,0);
gpio_set_direction(IO_PORTA_02,0);
gpio_set_output_value(IO_PORTA_02,0);
putchar('0');
flag = 1;
}
}
___interrupt
void timer_catch_isr(void)
{
CATCH_TIME_REG->CON |= BIT(14);
u16 bCap1 = CATCH_TIME_REG->PRD; //上次中断到这次的时间,可以用于计时
CATCH_TIME_REG->CNT = 0;
num++; //计数
}
void timer_100ms()
{
printf("num == %d\n",num);
num = 0;
}
void timer_catch_init()
{
printf("timer_catch_init\n");
//IO口配置
gpio_irflt_in(CATCH_GPIO);
gpio_set_direction(CATCH_GPIO, 1);
gpio_set_die(CATCH_GPIO, 1);
gpio_set_pull_up(CATCH_GPIO, 1);
gpio_set_pull_down(CATCH_GPIO, 0);
//设置时钟
SFR(JL_IOMAP->CON0, 5, 3, CATCH_TIMER); //TIMER5:7 irflt.h
CATCH_TIME_REG->CON = 0;
CATCH_TIME_REG->CON |= (0b10 << 2); //选择晶振时钟源:24MHz
CATCH_TIME_REG->CON |= (0b0001 << 4); //时钟源4分频
//设置周期,初始值,定时器模式
CATCH_TIME_REG->PRD = OSC_Hz / (4 * 1000);
CATCH_TIME_REG->CNT = CATCH_TIME_REG->PRD;
CATCH_TIME_REG->CON |= BIT(14);
CATCH_TIME_REG->CON |= (0b11 << 0);
request_irq(CATCH_IRQ_TIME_IDX, 5, timer_catch_isr, 0);
sys_timer_add(NULL,timer_100ms,100); //定时100ms读取中断次数
sys_timer_add(NULL,timer_ms,1); //设置生成波形,供中断采集,不准,非准确的1ms
}

上升下降沿切换触发:
701N系列SDK,利用定时器捕捉实现IO中断
void io_ext_interrupt_test(void)
{
set_io_ext_interrupt_cbfun(my_io_isr_cbfun);
io_ext_interrupt_init(0, IO_PORTA_04, 1);
io_ext_interrupt_init(1, IO_PORTB_01, 1);
io_ext_interrupt_init(2, IO_PORTC_02, 1);
io_ext_interrupt_init(3, IO_PORTG_01, 1);
extern void wdt_clear();
while (1) {
wdt_clear();
}
}
4. 拓展:定时器us级延时功能
//ums timer
#define USER_UDELAY_TIMER JL_TIMER3//微秒延时使用的定时器 地址 可以其他的定时器
#define USER_UDELAY_TIMER_IRQ IRQ_TIME3_IDX//微秒延时使用的定时器 终端号
/**
* @brief 初始化 自定义微秒延时定时器
*
*/
void user_udelay_init(void){
bit_clr_ie(USER_UDELAY_TIMER_IRQ);
}
/**
* @brief 自定义微秒延时接口
*
* @param usec 延时时间 单位微秒
*/
__attribute__((weak)) void user_udelay(u32 usec){
USER_UDELAY_TIMER->CON = BIT(14);//0x4000;
USER_UDELAY_TIMER->CNT = 0;
USER_UDELAY_TIMER->PRD = (24* usec/4) ;//延时时间=时钟源频率/分频系数*计数次数
USER_UDELAY_TIMER->CON = BIT(0)|BIT(3)|BIT(4);//BIT(0)定时计数模式 BIT(3):晶振为时钟源 BIT(4):4分频
while ((USER_UDELAY_TIMER->CON & BIT(15)) == 0);
USER_UDELAY_TIMER->CON = BIT(14);
}
void user_delay_us(u32 usec)
{
user_udelay(usec);
}
5. [sys_timer与usr_timer定时器]
sys_timer与usr_timer定时器各接口详细查看timer.h文件
- sys_timer由systimer线程提供时基,在哪个线程中add的sys_timer定时(如sys_timer_add),systimer线程中的定时时基到了,就由systimer线程发出事件通知对应的add线程执行回调函数(sys_timer系统定时器最小精度为10ms,该定时适用于某些不能在中断里面执行并且定时间隔较长的定时任务)
- usr_timer定时器是由硬件定时器timer1提供的时基, 在其他线程add的usr_timer,在硬件定时器中时基到时,在硬件定时器定时中断中执行回调函数(所以回调函数不能写的过长),相对于sys_timer定时时基更加准确;并且使用usr_timer定时器时,不建议关闭系统总中断(会影响usr_timer硬件定时器中断执行回调函数)
- PWM
背景知识简介:
PWM:脉冲宽度调制,一种模拟控制方式,根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置,来实现晶体管或MOS管导通时间的改变,从而实现开关稳压电源输出的改变。
重要参数介绍:
PWM波形最重要的两个参数为:频率,占空比。
占空比:一个周期内高电平占整个周期的时间比。 如图,高电平的时间(1ms) + 低电平的时间(3ms) = 4ms(一个周期)
频率 = 1s / 4ms = 250HZ
高电平时间 / 一个周期时间 = 1ms / 4ms = 25%(占空比)
PWM功能应用:
PWM功能一般用于电机驱动,灯光亮度调制等。通过设置PWM波形输出的占空比来设置电机输出的大小或者灯光的亮度。
PWM波形频率和占空比调整精度的关系:
PWM波形一般由一个固定的时钟控制产生,一般时钟的频率是固定的,所以有以下关系: 时钟 = 频率 * 周期时间。
周期时间:就是一个周期内可以改变的精度,如果一个周期内有100个时间点,那占空比控制的精度就是1/100。
所以当提高频率时,占空比控制的精度就会下降。
芯片如何产生PWM波形:
使用mcpwm.c文件的timer_pwm_init函数产生。
timer_pwm_init函数介绍:
#define OSC_Hz 24000000
/**
* @param JL_TIMERx : JL_TIMER0/1/2/3/4/5
* @param fre : 频率,单位Hz,不小于95
* @param duty : 初始占空比,0~10000对应0~100%
* @param port : pwm脚,可选硬件脚,也可选非硬件脚。(建议选择硬件引脚)
* @param output_ch : 映射通道,当pwm脚选择非硬件脚时有效,这时我们给他分配output_channel 0/1/2
*/
int timer_pwm_init(JL_TIMER_TypeDef *JL_TIMERx, u32 fre, u32 duty, u32 port, int output_ch)
{
u32 hw_port;
switch ((u32)JL_TIMERx) {
case (u32)JL_TIMER0 :
hw_port = IO_PORTA_05;
break;
case (u32)JL_TIMER1 :
hw_port = IO_PORTC_04;
break;
case (u32)JL_TIMER2 :
hw_port = IO_PORTB_03;
break;
case (u32)JL_TIMER3 :
bit_clr_ie(IRQ_TIME3_IDX);
hw_port = IO_PORTB_05;
break;
case (u32)JL_TIMER4 :
hw_port = IO_PORTA_01;
break;
case (u32)JL_TIMER5 :
hw_port = IO_PORTB_07;
break;
default:
return (-1);
}
if ((output_ch == (-1)) && (hw_port != port)) {
//not support output_ch
return (-1);
}
//初始化timer
JL_TIMERx->CON = 0;
JL_TIMERx->CON |= (0b10 << 2); //选择晶振时钟源:24MHz
JL_TIMERx->CON |= (0b0001 << 4); //时钟源4分频
JL_TIMERx->PRD = OSC_Hz / (4 * fre); //设置周期
JL_TIMERx->PWM = (JL_TIMERx->PRD * duty) / fre; //0~10000对应0~100g
JL_TIMERx->CNT = 0; //清计数值
JL_TIMERx->CON |= (0b01 << 0); //计数模式
if (hw_port == port) {
gpio_set_die(hw_port, 1);
gpio_set_pull_up(hw_port, 0);
gpio_set_pull_down(hw_port, 0);
gpio_set_direction(hw_port, 0);
JL_TIMERx->CON |= BIT(8); //PWM使能
} else {
gpio_output_channle(port, output_ch);
return 1;
}
return 0;
}
参数输入如上图所示,注意点如下:
每个定时器都对应一个固定的IO口,如TIMER1->IO_PORTA_12,如果在芯片上能找到对应定时器的对应脚则优先使用。没有对应IO口的情况下,则可以任选一个IO口,然后通过通道映射的方式来使选择的IO口产生PWM。 示例:
timer_pwm_init(JL_TIMER3, 2000, 5000, IO_PORTB_05, 0); //对应IO口产生
timer_pwm_init(JL_TIMER3, 2000, 5000, IO_PORTB_04, CH2_T3_PWM_OUT); //通道映射
通道可以在gpio.h文件查询到,CHx_Tn_PWM_OUT,x代表通道x,n代表定时器,每个通道只能映射一个IO口,定时器必须跟选择的定时器一致。
备注:定时器无论是正常对应IO口输出,映射输出都只会产生一个PWM波形,由最后执行的函数为准,且映射输出时,定时器对应的IO口也会产生对应波形,所以最优先选择定时器对应的IO口,避免造成IO口浪费。占空比,公版代码的占空比输入不是0-1000控制0-100%,如果需要改成此效果改此处: JL_TIMERx->PWM = (JL_TIMERx->PRD * duty) / 10000; //0~10000对应0~100%
时钟选择: JL_TIMERx->CON = (0b10 << 2); //选择晶振时钟源:24MHz
公版SDK默认选择晶振时钟源,695,696均为24MHZ,608由于不带晶振的缘故需要选择系统时钟源,配置如下:
JL_TIMERx->CON = (0b00 << 2); //选择系统时钟源
695,696的系统时钟源一般会比晶振时钟源的时钟高,695为60MHZ,如果需要产生高频率的PWM波形时可以改为系统时钟源。产生PWM波形失败的可能原因: (1) IO口被其他地方占用,程序是按前文配置,选择的IO口没有输出对应的波形时,可以通过代码编辑搜索看看是否IO口被其他地方占用;
(2) 定时器被占用,一般这种情况会导致程序复位或者死机。一般TIMER1为系统自己本身使用的时钟,不建议使用。TIMER2默认用于UI显示,建议关掉UI显示再使用TIMER2;初始化PWM时闪灯解决办法

消除占空比变为0时的一周期的高电平时间问题
void set_timer_pwm_duty(JL_TIMER_TypeDef *JL_TIMERx, u32 duty)
{
if(duty == 0){
if(JL_TIMERx->PWM == 0){
return;
}
//计时标志,进来此函数前PB3已经设置为高电平,PB3部分控制可省略
JL_PORTB->OUT &= ~BIT(3);
/*周期内低电平时期进入此函数,强制延长半个到一个周期等下一个周期下降沿再触发,
防止一个周期最后1us左右时间触发此函数导致的程序执行时间不够导致极短时间的高电平*/
if(JL_TIMERx->CNT > JL_TIMERx->PWM){
while(JL_TIMERx->CNT > JL_TIMERx->PWM){
delay(1);
}
while(JL_TIMERx->CNT < JL_TIMERx->PWM){
delay(1);
}
}
//周期内高电平时间进入此函数,等周期内低电平时间触发
while(JL_TIMERx->CNT < JL_TIMERx->PWM){
delay(1);
}
//关闭PWM波形
JL_TIMERx->CON &= ~BIT(8);
//时间标志
JL_PORTB->OUT |= BIT(3);
}
else{
//开启PWM
JL_TIMERx->CON |= BIT(8);
}
JL_TIMERx->PWM = (JL_TIMERx->PRD * duty) / 10000; //0~10000对应0~100%
//时间标志
JL_PORTB->OUT &= ~BIT(3);
}
void timer_pwm_test(void)
{
//PB3为了计时设置,可以去掉
gpio_set_pull_down(IO_PORTB_03,0);
gpio_set_pull_up(IO_PORTB_03,0);
gpio_set_direction(IO_PORTB_03,0);
timer_pwm_init(JL_TIMER3, 100000, 2000, IO_PORTB_05, 0);
}
//测试按键消息
case KEY_SET_PWM:
log_info("KEY_SET_PWM\n");
static int pwm_flag = 0;
JL_PORTB->OUT |= BIT(3);
if(pwm_flag){
set_timer_pwm_duty(JL_TIMER3,0);
pwm_flag = 0;
}
else{
set_timer_pwm_duty(JL_TIMER3,5000);
pwm_flag = 1;
}
break;
问题波形1(占空比变为0时1周期的高电平):
问题波形2(周期末尾操作寄存器执行时间不够导致波形顺延到下周期才执行):
DEMO
- AC696X 对于使用 AC696X 需要 PWM 功能的, 可以见如下参考例子, 详情请见网盘链接:
链接: https://pan.baidu.com/s/166ibbi08er-tHo5LVHr11Q
提取码: s2qq
- MCPWM
AC695
- MCPWM输出的IO
H L
---CH0--- IO_PORTA_00 IO_PORTA_01 (PA1输出没有波形)
---CH1--- IO_PORTB_00 IO_PORTB_02 (不开死区可以,开死区没输出)
---CH2--- IO_PORTB_04 IO_PORTB_06
---CH3--- IO_PORTB_09 IO_PORTB_10
---CH4--- IO_PORTA_09 IO_PORTA_10
---CH5--- IO_PORTC_04 IO_PORTC_05 (不开死区可以,开死区没输出。)
- 示范demo
void mcpwm_test(void);
AC695x实现两个PWM波形的相位差
参考文档资料:
链接:https://pan.baidu.com/s/14nYi-vAcxWu5dO9_6xtxJg
提取码:8888
AC695X 机电PWM DEMO
链接:https://pan.baidu.com/s/1g4YbK3YCWwZiRvt6_YzxNw
提取码:8888
AC696
注意:
如需要输出PWM5,且当PA3和PB7都配置为输出方向,两个IO均会输出PWM5

- UART
- uart基本知识 通用异步收发传输器称为UART。由发送端uart将发送端的并行数据转换为串行数据,以串行方式将其发送到接收端的,然后由接收端的uart将串行数据转换为并行数据以用于接收设备的正常处理。


- uart-SDK应用
2.1 uart串口打印调试
uart串口打印调试使用uart0,以下在板机文件中配置串口打印的IO口和波特率
uart打印IO口,可配置为芯片任意IO口
以下为串口打印开库打印总开关和打印系统时间和异常断言信息

2.2 充电仓/智能仓串口通讯
充电仓/智能仓通讯使用uart1,SDK使用时需要使能TCFG_TEST_BOX_ENABLE,充电仓通讯IO口默认配置为芯片LDO_IN绑定的IO口。通过该通讯口可以实现内置充电/uart通信/串口升级。
2.3 串口在线调音
本截图以696x-170SDK-新音箱配置工具&调音工具为案例,串口在线调音的具体方法可参考以下文档
新Tools的音效调试使用.otlEffectTool(eq)调试工具说明文档.pdfAC897AD697系列的EQ调试方法-V3.pdf
2.4 PCM串口调试

本截图以696x-170SDK做案例,pcm串口调试主要用于通话写卡调试,将通话时mic采集的原始数据、mic采集经过算法处理的数据和远端通话传过来经过DAC放出的数据写到SD卡中,使用写卡工具解析数据,再进一步分析调试。
需要注意的是PCM串口调试使用的是uart1,不要与其他使用uart1的功能做冲突;pcm调试的IO口也不要与uart0串口调试IO口做冲突。
PCM写卡工具使用说明和数据导出脚本参考 https://kdocs.cn/l/cpyHfHMfywJS
pcm串口写卡数据分析可参考通话数据分析方法-PCM写卡 V1.0.pdf
3. uart-各demo案例及应用 AC696X demo1:
链接:https://pan.baidu.com/s/12kkT4KoKb4svLmWH3mfh9g
提取码:jiel
AC696X demo2:
链接:https://pan.baidu.com/s/1YeC8VRFNX85EbvBdnYMsDQ
提取码:jiel
3.1 AC696X 串口单io进行收发数据
注意:debug串口初始化之前需要添加JL_UART0->CON0 = BIT(0);确保通信串口不被debug串口占用
#define UART_CLK clk_get("uart")
#define UART_OT_CLK clk_get("lsb")
#define UART_BAUD 1000000
#define UART_RX_TIMEOUT 1000//ms
#define UART JL_UART0//JL_UT1
static u8 uart_tx_buff[128];__attribute__((aligned(4)));
static u8 uart_rx_buff[128];__attribute__((aligned(4)));
static void test_uart_set_dma_dir(u32 dir){
if (dir) {
JL_PORTB->DIR |= BIT(5);
UART->CON1 &= ~BIT(4);
} else {
UART->CON1 |= BIT(4);
JL_PORTB->DIR &= ~BIT(5);
}
}
void test_uart_tx_data(u8 *buf, u32 len){
test_uart_set_dma_dir(0);
UART->TXADR = (u32)buf;
UART->TXCNT = len;
__asm__ volatile("csync");
while ((UART->CON0 & BIT(15)) == 0) { //TX IDLE
}
UART->CON0 |= BIT(13);
test_uart_set_dma_dir(1);
}
int test_uart_rx_data(u32 addr, u32 len){
wdt_clr();
test_uart_set_dma_dir(1);
UART->RXSADR = (u32)addr;
UART->RXEADR = (u32)(((addr + len) + 3) / 4 * 4);
/* UART->RXEADR = (u32)(addr + len); */
UART->RXCNT = len;
__asm__ volatile("csync");
while (1) {
if (UART->CON0 & (BIT(14) | BIT(11))) {
/* if (UART->CON0 & (BIT(11))) { */
/*if (UART->CON0 & (BIT(14))) {*/
break;
}
}
UART->CON0 |= BIT(7);
UART->CON0 |= BIT(10) | BIT(12);
__asm__ volatile("csync");
int rx_len = UART->HRXCNT;
UART->CON0 |= BIT(12) | BIT(10);
return rx_len;
}
void test_uart_init(){
JL_IOMAP->CON0 &= ~(3 << 3); //ut0_c
JL_IOMAP->CON0 |= (2 << 3);
JL_IOMAP->CON3 |= (1 << 3);
JL_PORTB->HD |= BIT(5);
JL_PORTB->PU |= BIT(5);
JL_PORTB->PD &= ~BIT(5);
JL_PORTB->DIR |= BIT(5);
UART->CON0 = BIT(13) | BIT(12) | BIT(10);
UART->CON0 = BIT(6) | BIT(0)| BIT(5);
UART->CON0 |= BIT(13) | BIT(12) | BIT(10);
UART->BAUD = (UART_CLK / UART_BAUD) / 4 - 1;
u32 timeout=UART_RX_TIMEOUT;//ms
UART->OTCNT = timeout * (UART_OT_CLK / 1000);//1000us
}
void test_uart_fun(void){
wdt_close();
test_uart_init();
sprintf(uart_tx_buff,"=================0x%x 0x%x 0x%x\r\n",JL_UART0->CON0,JL_UART1->CON0,JL_UART2->CON0);
test_uart_tx_data(uart_tx_buff,sizeof(uart_tx_buff));
int len=0;
while (1){
memset(uart_rx_buff,0,sizeof(uart_rx_buff));
memset(uart_tx_buff,0,sizeof(uart_tx_buff));
len=test_uart_rx_data(uart_rx_buff,32);
test_uart_tx_data(uart_rx_buff,len);
printf(uart_rx_buff);
}
}
3.2 AD697 简单串口收发demo
在app_config.h进行配置
//*********************************************************************************//
// uart demo //
//*********************************************************************************//
#define UART_DEMO_EN 1
#define UART_DEMO_TX IO_PORT_DP
#define UART_DEMO_RX IO_PORT_DM
#define UART_DEMO_BAUD 115200
在user_uart_demo.c中有收发简单例程
void uart_rx_data_analyze(u8 *data, u16 len)
{
u32 offset = 0;
test_printf("uart rx date len = %d, data = \n", len);
test_putbuf(data, len);
u8 head = 0xaa;
//回复接收到的data
// offset += uart_tx_data_input(head, offset, 1);
offset += uart_tx_buff_input(data, offset, len);
test_printf("offset = %d\n", offset);
if (offset) {
//发送uart_txbuf中
uart_bus->write(uart_txbuf, offset);
}
}
具体看demo文件
【金山文档】 AD697_串口demo_20220817
https://kdocs.cn/l/cmdY4d375c95
3.3 UART串口透传(较公版代码稳定)
【金山文档】 uart_dev.c
https://kdocs.cn/l/cltEjESEkOJX
3.4 从UART获取数据写到TF卡demo
【金山文档】 20220726_AC695v310_uart2tf_demo
https://kdocs.cn/l/cojkOh8aCofw
FAQ
- 出现开启串口后部分IO口异常,换个IO口正常的情况 原因:以696为例,这个寄存器默认的值为1,所以出现实际已经使用其他IO口做串口,这个IO口仍然被占用(被占用的IO口是该串口对应的A组口)
解决方式:
JL_IOMAP->CON3 &= ~BIT(7);//696UART1的解决方式,串口函数初始化前调用
//其他系列,其他UART见用户手册
- SPI
- SPI基本知识 SPI接口是一种同步串行总线(Serial Peripheral Interface)多用于Flash存储器、LCD控制器等外围器件的通讯接口。【注意:主控的SPI端口由芯片硬件确定无法进行IO映射(701x芯片可以);在使用SPI时,spi的时钟CLK脚从硬件上就会输出时钟波形,不能作为输出功能,不要用于驱动其他外设(SPI引脚可以映射的话则可以使用)】

- SPI-SDK应用 SPI在board_ac695x_demo.c中的配置选项

2.1 SPI外挂norflash
2.1.1例子
以695xSDK为例,实现外挂 flash录音、外挂 flash 提示音播放、外挂 flash 分区(fat+tone+rec)功能等。详细程序配置步骤和驱动方法可见SDK程序包资料doc文件夹下的外挂flash 相关操作文档[AC695x外挂Flash分区操作说明文档V2.0.pdf]。
AC695x外挂Flash分区操作说明文档V2.0.pdf
2.1.1生成程序可以使用的外挂flash烧录文件
以696 170为例:
demo
步骤如下:
- 下载FAT文件系统制作工具FAT.7z
- 使用工具生成带fat文件系统的img文件

- 运行packres.bat脚本生成res.bin文件
- 此时该res.bin文件可以提供给flash的烧录厂去烧录
- 在开发时如果需要在下载程序的同时把制作好的res.bin文件下载到外挂flash中
- 把res.bin放入download.bat同级目录,并在脚本中添加烧写命令-ex_flash res.bin


- ini中配置外挂flash信息

- 在下程序时有如下信息输出

- 把res.bin放入download.bat同级目录,并在脚本中添加烧写命令-ex_flash res.bin
- 程序调用外挂flash资源,以播放其中的bt.mp3提示音为例
// “storage/fat_nor/C/” 为挂载外挂flahs时的设备地址
tone_play_by_path("storage/fat_nor/C/bt.mp3", 0);
2.2 外挂nandflash设备
【目前NANDFLASH设备只支持读操作】
AC695N系列添加方法
链接:https://pan.baidu.com/s/1vzV_o6UBURQ4wyvHh-LYHQ
提取码:8888
下载后请对比公版SDK,对比查看修改。
AC104N系列添加方法
链接:https://pan.baidu.com/s/1grbYIfmkd_nySn7eHVhmBA
提取码:8888
下载后请对比公版SDK,对比查看修改。
2.3 SPI驱动LCD屏
LCD/点阵屏/彩屏等UI详细知识可参考文档UI相关应用.otl
LCD与主控硬件接口:(一组SPI口+3个普通GPIO)如下
| 主控接口1 | LCD屏接口 |
|---|---|
| LCD_DATA | SDA |
| LCD_CLK | SCK |
| LCD_A0 | RS |
| LCD_RES | RST |
| LCD_CS | CS |
以695为例的SPI推动LCD12864demo程序:
【金山文档】 LCD12864_AC695N_soundbox_sdk_release_3.1.0
LCD段码屏的驱动
LCD段码屏的的详细内容参考“AC695N用户手册V1.2.pdf”中第13章内容:
注意点:LCD断码屏的COM 口只能顺序使用,不能打乱顺序使用。
以696x310SDK为例demo程序如下:
https://pan.baidu.com/s/19OQKNMsSTVAr76FPqfXXAw
提取码:8888
2.4 SPI驱动RGB灯
SPI驱动RGB灯的详细内容参考文档UI相关应用.otl中的【单线串行控制幻彩灯珠】资料,包含695/696/700/701驱动RGB灯的详细方法,需要注意查看文档中的【代码注意点】
8. 内部FLASH
内部FLASH空间添加用户自定义区域
参考资料: 添加用户自定义区域 :https://kdocs.cn/l/cszLZSN40zWI SDK demo: 【金山文档】 ac696n_soundbox_sdk_v1.3.3_用户保留区 ac696n_soundbox_sdk_v1.3.3_用户保留区.rar
补充说明如下:
- 修改 isd_config.ini 文件, 配置 user_info 区域大小和名称
isd_config.ini 修改如下:
USER_ADR=AUTO;
USER_LEN=0x1000;
USER_OPT=1;
添加 user 存储区域描述, 名称为”USER”。需要跟程序上的fopen对应。如下图。
USER_ADR=AUTO; 表示地址由工具自动分配, 默认即可, 用户不需要修改;
USER_LEN=0x1000; 表示区域大小, 用户可以根据需求配置;
USER_OPT=1; 表示下载代码时对该区域的操作, 用户一般不需要修改;
2. 修改 sdk_ld 文件中的链接地址 
- 如果是0x1E00020的话,就改成0x1E00040 3. 添加 user_info_demo.c 文件中的代码测试 相关接口:读写接口跟文件系统接口一致,但由于flash的特性需要写之前需要自己去管理擦除user_info区域。
//打开文件
fp = fopen("mnt/sdfile/app/user", "wr"); //字符: mnt/sdfile/app/ 固定; 字符user对应ini文件里配置的名称
//文件偏移
fseek(user_fp, -wbuf_len, SEEK_CUR); //把文件指针移到刚写的位置
//把buf写入区域
ret = fwrite(user_fp, wbuf, wbuf_len);
//读出写入buf内容
fread(user_fp, rbuf, rbuf_len);
//擦除区域操作(物理地址)
norflash_ioctl(NULL, erase_cmd, erase_addr);
#if 1//for test
FILE *user_fp; //区域读写句柄
struct vfs_attr attr = {0}; //user_info区域属性, sclust: 区域地址, fsize: 区域大小
extern FILE *user_info_zone_init(struct vfs_attr *attr);
user_fp = user_info_zone_init(&attr);
int ret = 0; //区域操作返回值
printf("fsize=%d,sclust=%d,attr=%d",attr.fsize,attr.sclust,attr.attr);
u8 r_test_buf[80] = {0};
u8 test_buf[32] = {0};//test buffer
for(int i=0;i<sizeof(test_buf);i++) {
test_buf[i]=i;//给数组赋值
}
// 读出刚才写入的内容, 检查是否写入正确;
fseek(user_fp, 0, SEEK_SET);
ret = fread(user_fp, r_test_buf, sizeof(test_buf));//读32个字节
if(ret != sizeof(test_buf)) {
printf("read err\r\n");
}
put_buf(r_test_buf,sizeof(r_test_buf));
//写 从起始地址开始写
fseek(user_fp, 0, SEEK_SET);
ret = fwrite(user_fp, test_buf, sizeof(test_buf));
printf("write len = %d\r\n",ret);
if(ret != sizeof(test_buf)) {
printf("write err\r\n");
printf("earse all\r\n");
user_info_zone_earse(user_fp, &attr);
}
// 读出刚才写入的内容, 检查是否写入正确;
fseek(user_fp, -sizeof(test_buf), SEEK_CUR); //把文件指针移到刚写的位置
ret = fread(user_fp, r_test_buf, sizeof(test_buf));//读32个字节
if(ret != sizeof(test_buf)) {
printf("read err\r\n");
}
put_buf(r_test_buf,sizeof(r_test_buf));
while(1)
{
wdt_clear();
};
#endif
注:
1. FLASH的对齐方式可以从code block的编译输出信息中看到(256Bytes对齐、4K对齐),如下图:
b.4K对齐的FLASH如果使用page擦除,会导致擦除的sector出现不可预知的错误,所以操作FLASH的时候请考虑操作的规范性
9. IIC
IO口及其他配置定义
此处以695_demo板级配置文件举例:
IIC基础使用函数
此处以695的软件IIC举例,同时也建议客户使用软件IIC来进行IIC各项操作。
函数文件:iic_soft.c iic_soft.h
IIC一般使用方式
#define WRITEORDER 0x58 //写命令
#define READORDER 0x59 //读命令
参考使用方式
//指定寄存器地址写一个字节
u8 I2C_write_byte(u8 writeAddr,u8 byte)
{
u8 ret = 0;
iic_start(iic); //I2C启动
ret += iic_tx_byte(iic, WRITEORDER); //写命令
ret += iic_tx_byte(iic,writeAddr); //写地址
ret += iic_tx_byte(iic,byte); //写一个字节
iic_stop(iic); //产生一个停止条件
if(ret < 3){
return 1;
}
else{
return 0;
}
}
//指定寄存器地址读一个字节
u8 I2C_read_byte(u8 readaddr)
{
u8 ret = 0;
u8 data = 0;
iic_start(iic); //I2C启动
ret += iic_tx_byte(iic, WRITEORDER); //写命令
ret += iic_tx_byte(iic,readaddr); //read地址
iic_stop(iic); //I2C停止
iic_start(iic); //I2C启动(写转为读命令,需要再次启动I2C)
ret += iic_tx_byte(iic, READORDER); //读命令
data = iic_rx_byte(iic, 0); //读操作
iic_stop(iic); //产生一个停止条件
if(ret < 3){
return 1;
}
else{
return data;
}
}
/**********************************************************
//在指定地址写入数据
//writeset_addr :写入数据的器件地址
//writeAddr :寄存器地址
//buf :要写入的数据
//len :写入的长度
**********************************************************/
u8 IIC_write_reg(char writeset_addr,char writeAddr,const void *buf, int len)
{
u8 ret = 0;
u8 i = 0;
u8 ack = 0;
iic_start(iic); //I2C启动
ret += iic_tx_byte(iic, writeset_addr); //写器件地址
ret += iic_tx_byte(iic,writeAddr); //写寄存器地址
for (i = 0; i < len; i++) {
ret += iic_tx_byte(iic, ((char *)buf)[i]);
}
iic_stop(iic); //产生一个停止条件
if(ret < len+2){
return 1;
}
else{
return 0;
}
}
/**********************************************************
//读指定地址的数据
//readaddr :读取数据的目的地址
//buf :读出的数据
//len :读出数据的长度z
**********************************************************/
u8 I2C_read_reg(u8 readaddr,void *buf, int len)
{
u8 ret = 0;
iic_start(iic); //I2C启动
ret += iic_tx_byte(iic, WRITEORDER); //写命令
ret += iic_tx_byte(iic,readaddr); //read地址
iic_stop(iic); //I2C停止
iic_start(iic); //I2C启动(写转为读命令,需要再次启动I2C)
ret += iic_tx_byte(iic, READORDER); //读命令
iic_read_buf(iic, &buf, len); //读操作
iic_stop(iic); //产生一个停止条件
if(ret < 3){
return 1;
}
else{
return 0;
}
}
外挂FM IIC通信参考
AC696X:
ac696n_soundbox_sdk_v0.2.3(5807 FM IIC 脚和 卡口复用 )
链接:https://pan.baidu.com/s/1GY2yNSr-ZcbKF6uuCScbdg
提取码:8888
AC695X:
AC695N_soundbox_sdk_release_0.0.7-外挂收音RDA5807复用SD卡
链接:https://pan.baidu.com/s/1rdX549Jzj9-heOauSDiOiw
提取码:8888
AC695N_soundbox_sdk_release_3.0.1(外挂5807收音正常版本)
链接:https://pan.baidu.com/s/1J6TLCDgyWxRETnwViFrosw
提取码:8888
AC608X:
ac608n_soundbox_sdk_v1.1.1-iic复用SD卡
链接:https://pan.baidu.com/s/1AGxayv5yPzWeDixSbQDedg
提取码:8888
AC700X:
AC7006F4SDK121外挂收音芯片5807参考DEMO
链接:https://pan.baidu.com/s/1P1djXnYdeZFfmbM_NRO-YQ
提取码:8888
- SPDIF模式(HDMI、光纤、同轴)
- SD卡&MMC卡&U盘&假U盘 MMC卡:MultiMediaCard,多媒体存储卡
SD卡:Secure Digital Memory Card
常用区分方式:如下
- SD相关应用
- SD卡应用相关配置

- 三种检测方式

- 流程 检测到卡插入或拔出-->触发相应的公共设备事件处理-->解码设备上下线,设备挂载等处理-->设备扫盘
- 如何使能MMC卡功能 如果需要支持市场上这种MMC卡程序上需要做以下配置
- 程序上打开MMC卡的配置 SDK\apps\soundbox\log_config\lib_driver_config.c
const int sdx_can_operate_mmc_card = 1;
SD卡的检测改为CLK或者IO检测。不能使用CMD检测

U盘不读问题处理方法
先检查U盘文件系统是否是支持的文件系统。 默认支持 FAT12,FAT16,FAT32,exFAT
不支持:NTFS常见U盘不读处理方法 以下所有方法只在ac608n_soundbox_sdk_v1.2.1和ac696n_soundbox_sdk_v1.3.0
,ac696n_soundbox_sdk_v1.2.3验证过。旧版本的可以参照一下。
对于608n省晶振系列和696系列:
有些U盘反应较慢,host过了超时时间会认为U盘没有回应导致不读取U盘,挂载不成功
3. 在程序上开启log打印。
观察打印有没有出现这个打印
若有可以修改
第1点没有改善可以看第2点。
2.在apps\common\usb\usb_common_def.h文件中修改
超时时间,复位持续时间,尝试次数加长,以等待slve回应,。
4. 程序若使能了PC模式,则USB OTG 会第一时间以host端发送数据,若从机没有回应会认为此时应该担任slave的角色。有些U盘会插入后发送信息会较慢,导致USB OTG 认为插入的是主机,从而切换到从机模式,导致U盘不读。 
- 对于6965e使用的是USB和SD卡复用引脚,可以尝试一下改法:

608N省晶振系列:
608系列可以参照第1点、第2点、第3点。若以上第1点和第2点没能改善,尝试下面的方法。
修改usb_host.c文件
注意:608N省晶振系列对于东芝U盘(Toshiba),采用以上办法后,仍然有概率不能挂载成功,这个问题暂时没有办法解决。
4. U盘和卡复用相关 (SD、USB复用IO介绍请查找对应sdk介绍文档)
- U盘和卡复用后,默认SDK是不支持卡用CMD检测的(建议使用CLK检测)。若生产出现卡座出问题,无法使用CLK检测。一定要改成CMD检测,需要按以下修改,并多做测试:(以下是AC696X 案例)


5. USB DM接地/DM&DP短接检测(假U盘) 注意:SD&USB复用的方案,由于DM跟SD的dat是同一个IO。不能使用DM&DP短接的方式。否则会导致SD卡不正常
使用方法:sys_timer_add(NULL,fake_udisk_scan,100);
观察打印:三个状态对应的打印
DP接地/DM&DP短接: y_printf("fake udisk online 2\n");
拨出: y_printf("fake udisk offline\n");
//使用方法:sys_timer_add(NULL,fake_udisk_scan,100);
#include "usb/device/usb_stack.h"
void fake_udisk_scan(void *ptr)
{
static u16 online_cnt = 0;
static u16 outline_cnt = 0;
//static u16 fake_udisk_online = 0;
u32 otg = usb_otg_online(0);
//仅通过判断otg状态进行判断
printf("otg %x\n",otg);
extern int get_sd_status(void);
get_sd_status();
if((otg != HOST_MODE) && (otg != SLAVE_MODE)) {
//通过检测DP下拉,判断是不是假U盘
u32 usb_con0, usb_io_con0;
usb_con0 = JL_USB->CON0;
usb_io_con0 = JL_USB_IO->CON0;
//单纯 DP接地
gpio_direction_input(IO_PORT_DP);
gpio_set_die(IO_PORT_DP, 1);
gpio_set_pull_up(IO_PORT_DP, 1);
gpio_set_pull_down(IO_PORT_DP, 0);
gpio_direction_output(IO_PORT_DM,0);
gpio_set_die(IO_PORT_DM, 1);
printf("1 IO_PORT_DP DM %x %x\n",gpio_read(IO_PORT_DP),gpio_read(IO_PORT_DM));
if(gpio_read(IO_PORT_DP)==0){
online_cnt++;
if(online_cnt == 5) {
if(fake_udisk_online == 0) {
fake_udisk_online = 1;
app_task_switch_to(APP_USER_TASK);
y_printf("fake udisk online 2\n");
}
}
gpio_set_pull_up(IO_PORT_DP, 0);
//!!!这里直接返回
JL_USB->CON0 = usb_con0;
JL_USB_IO->CON0 = usb_io_con0;
return;
}
gpio_set_pull_up(IO_PORT_DP, 0);
JL_USB->CON0 = usb_con0;
JL_USB_IO->CON0 = usb_io_con0;
// printf("JL_USB->CON0 %x %x %x\n",JL_USB->CON0,JL_USB->CON0 & BIT(9),JL_USB->CON0 & BIT(15));
// if(JL_USB->CON0 & BIT(9) && (JL_USB->CON0 & BIT(15)) == 0) {
// online_cnt++;
// if(online_cnt == 5) {
// if(fake_udisk_online == 0) {
// fake_udisk_online = 1;
// y_printf("fake udisk online2\n");
// }
// }
// //!!!这里直接返回
// return;
// }
}
if(fake_udisk_online) {
fake_udisk_online = 0;
y_printf("fake udisk offline\n");
// app_task_msg_post(USER_MSG_BT_TASK_MODE, 0);
app_task_switch_to(APP_BT_TASK);
}
online_cnt = 0;
}
- suspend和resume操作引脚
//0 : cmd
//1 : clk
//2 : data
extern u8 sd_io_suspend(u8 sdx, u8 sd_io);
extern u8 sd_io_resume(u8 sdx, u8 sd_io);
- 时钟&晶振
- 省晶振设置 各系列省晶振实现方法.otl
- 晶振输出能力设置
- AC695 系列晶振幅值强输出 在 setup.c 的 setup_arch 函数增加如下语句。
/*范围是 0x0-0x1c, 0x1c 最大*/
void clk_hcs12v_osc(u8 value);
clk_hcs12v_osc(0x09);

- AC689系列-BR52晶振幅值强输出
void xosc_set_sys_cfg(u32 sys_hcs, u32 sys_cls, u32 sys_crs);
// 要在时钟初始化之前调用
// 最大0xf?晶振振幅
// 默认值参数:0xc 0 0:幅值档位、cls&crs电容档位
xosc_set_sys_cfg(0xf, 0, 0);
- AC632N 系列晶振幅值强输出
///在这个 clk_early_init(TCFG_CLOCK_SYS_SRC, TCFG_CLOCK_OSC_HZ, TCFG_CLOCK_SYS_HZ);这个之后调用
SFR(JL_WLA->WLA_CON8, 6, 5, 0x0a); ///第四个形参 0x0a,的范围 0-0x1C,0x1C最大
3. 注册时钟变化的回调函数
这个切换时钟的时候,会触发回调函数
static void clock_critical_enter(void)
{
}
static void clock_critical_exit(void)
{
if (debug == NULL) {
return;
}
debug->BAUD = (uart_clk / debug_baud) / 4 - 1;
debug->OTCNT = uart_clk / 1000;
}
CLOCK_CRITICAL_HANDLE_REG(uart, clock_critical_enter, clock_critical_exit)
- 提高内核电压的办法 针对
- 将clock_mode 配置为CLOCK_MODE_USR

- 在set_up.c处,设置提高内核电压SYSVDD_VOL_SEL_129V

- 复用应用
- ADKEY与LED复用 ac696n_soundbox_sdk_v1.7.0:

- AD104TF卡复用外挂flash功能注意
- 默认有写好的流程,建议复用的引脚如下,
接线配置如下:
PA10 SDDAT SPICS
PA11 SDCLK SPIDI/DO(spi单线模式)
PA12 SDCMD SPICLK
PS:一定不可以将 SDCLK 和 SPICLK进行复用,由于两个都是clk脚,会导致设备通讯过程中有读取异常情况出现
TF卡复用外挂flash功能详细配置参考如下连接..链接:AD104TF卡复用外挂flash功能gitee配置参考
编码器应用
正常AB相编码器参考程序
【金山文档】 user_coder.c
https://kdocs.cn/l/cq8RsfjLPyRa
【金山文档】 user_coder.h
https://kdocs.cn/l/ctBflm0cNKi7
AD编码器参考使用方式
原理图:
原理:使用的IO_DET脚开上拉供电,开AD检测;
按硬件接法后,传统AB相编码器由于链接到AD口的电阻值的不同,以及原先AB相的时序不同,所以会出现电压的变化顺序不同,根据此变化来判断正反转。
参考程序
【金山文档】 user_ad_coder.c
https://kdocs.cn/l/cqtvWY1K6j0J
【金山文档】 user_ad_coder.h
https://kdocs.cn/l/cmYgHjGLtZE9
- IO口crossbar映射功能
1、crossbar 含义
关于JL701N/AC7000/AC897/AD697/AC698/BD19等系列的芯片,新加入了crossbar架构功能(Crossbar(即CrossPoint)被称为[交叉开关](https://baike.baidu.com/item/%E4%BA%A4%E5%8F%89%E5%BC%80%E5%85%B3)矩阵或纵横式交换[矩阵](https://baike.baidu.com/item/%E7%9F%A9%E9%98%B5/18069)。),即可以理解为重映射功能,增加了crossbar功能后,所有的io口都可以任意的映射,外设模块没有指定的io,可以使用crossbar重映射到任意的gpio上。例如SD卡、spi、iic、uart、ALINK、timer 、通用io等都可以使用重映射到指定的io中去使用。
注意:并不是所有IC系列都带用Crossbar功能,可以通过查看对应IC 的datasheet或芯片用户手册确认。
例如AC700X的:

2、使用注意点
Crossbar具有输出和输出的指定功能:在SDK中可以查看io_imap.h和io_omap.h里面看具有的映射io 功能。
1、io_imap :表示为映射输入功能,io的功能定义如下:
2、io_omap:表示为映射输出的功能,io功能如下:
3、USB功能的io只能是DM 和 DP,没有映射功能。
4、关于SDK中如何使用crossbar功能的接口说明,可以在SDK 中 include_lib/driver/cpu/br36/asm/gpio.h文件中查看:
注意:只有以上可以映射的功能,才可以映射使用,SDK没有定义的请不要去使用映射功能(数字可以映射,模拟的映射不了),硬件设计之前,请开发板或者demo验证一下是否可以,最简单的是查看程序是否有定义功能。
3、DEMO
1、以下是SPI 使用crossbar映射功能测试参考配置:
701N 120SDK与上图在映射上多了一个mode参数
700N接口说明
//=================================================================================//
//@brief: CrossBar 输出设置 API, 将指定IO口设置为某个外设的输出
//@input:
// gpio: 需要输出外设信号的IO口;
// fun_index: 需要输出到指定IO口的外设信号, 可以输出外设信号列表请查看io_omap.h文件;
// dir_ctl: IO口方向由外设控制使能, 常设为1;
// data_ctl: IO口电平状态由外设控制使能, 常设为1;
// low_power_mode: 低功耗状态是否保持io功能;
//@return:
// 1)0: 执行正确;
// 2)-EINVAL: 传参出错;
//@note: 所映射的IO需要在设置IO状态为输出配置;
//@example: 将UART0的Tx信号输出到IO_PORTA_05口:
// gpio_direction_output(IO_PORTA_05, 1); //设置IO为输出状态
// gpio_set_fun_output_port(IO_PORTA_05, FO_UART0_TX, 1, 1); //将UART0的Tx信号输出到IO_PORTA_05口
//=================================================================================//
int gpio_set_fun_output_port(u32 gpio, u32 fun_index, u8 dir_ctl, u8 data_ctl, u8 low_power_mode);
//=================================================================================//
//@brief: CrossBar 输入设置 API, 将某个外设的输入设置为从某个IO输入
//@input:
// gpio: 需要输入外设信号的IO口;
// pfun: 需要从指定IO输入的外设信号, 可以输入的外设信号列表请查看gpio.h文件enum PFI_TABLE枚举项;
// low_power_mode: 低功耗状态是否保持io功能;
//@return:
// 1)0: 执行正确;
// 2)-EINVAL: 传参出错;
//@note: 所映射的IO需要在设置IO状态为输入配置;
//@example: 将UART0的Rx信号设置为IO_PORTA_05口输入:
// gpio_set_die(data->rx_pin, 1); //数字输入使能
// gpio_set_pull_up(data->rx_pin, 1); //上拉输入使能
// gpio_direction_input(IO_PORTA_05); //设置IO为输入状态
// gpio_set_fun_input_port(IO_PORTA_05, PFI_UART0_RX); //将UART0的Rx信号设置为IO_PORTA_05口输入
//=================================================================================//
int gpio_set_fun_input_port(u32 gpio, enum PFI_TABLE pfun, u32 low_power_mode);
- GPCNT GPCNT 为时钟脉冲计数器, 用于计算两个时钟周期的比例, 即用一个已知时钟(主时钟) 计算
另一个时钟(次时钟) 的周期
主要用于脉冲计算以及脉冲频率计算等场景(硬件级别不会占用cpu),详细可以查看用户手册寄存器
下面为实现代码,代码中占用GPCNT_TIMER(驱动代码用timer0)用来做主时钟源原因是因为可以动态调节主时钟源从而做到动态调节测量范围
/*gpcnt.h*/
#ifndef __GPCNT_H__
#define __GPCNT_H__
#include "board_config.h"
#include "typedef.h"
#include "asm/clock.h"
#include "cpu.h"
#include "asm/gpio.h"
//主时钟单位时间(timering_ms) GTS(32*2^n)/ 主时钟频率(定时器频率)
//如果需要调节最小单位时间,可以通过修改主时钟频率(驱动代码默认最小单位为ms)
#define INPUT_CHANNLEL3_SRC_SEL(x) SFR(JL_IOMAP->CON1, 12, 4, x)
#define INPUT_CHANNLEL2_SRC_SEL(x) SFR(JL_IOMAP->CON1, 8, 4, x)
#define INPUT_CHANNLEL1_SRC_SEL(x) SFR(JL_IOMAP->CON1, 4, 4, x)
#define GPCNT_NUM JL_GPCNT->NUM //单位时间内获取到次时钟脉冲个数
extern void gpcnt_init_reg(u32 port,u32 ms); //使能gpcnt模块
extern void gpcnt_uninit_reg(void); //关闭gpcnt模块
#endif
#include "gpcnt.h"
#include "asm/power_interface.h"
static volatile u8 is_gpcnt_active = 1; //这个变量是用来控制是否进入sniff的,在测量时候可以先设置为1,退出后再设置为0
static u8 gpcnt_idle_query(void)
{
return !is_gpcnt_active;
}
REGISTER_LP_TARGET(gpcnt_lp_target) = {
.name = "pcnt",
.is_idle = gpcnt_idle_query,
};
const u32 timer_div[16] = {
/*0000*/ 1,
/*0001*/ 4,
/*0010*/ 16,
/*0011*/ 64,
/*0100*/ 2,
/*0101*/ 8,
/*0110*/ 32,
/*0111*/ 128,
/*1000*/ 256,
/*1001*/ 4 * 256,
/*1010*/ 16 * 256,
/*1011*/ 64 * 256,
/*1100*/ 2 * 256,
/*1101*/ 8 * 256,
/*1110*/ 32 * 256,
/*1111*/ 64 * 256,
};
static u32 timering_ms = 0;
___interrupt
AT_VOLATILE_RAM_CODE
void gpcnt_isr(void)
{
u32 gpcnt_num = GPCNT_NUM;
r_printf("%dms\n",timering_ms);
r_printf("%dNUM\n",gpcnt_num);
GPCNT_EN(0);
GPCNT_CLR_PEND(TRUE);
GPCNT_EN(1);
}
void gpcnt_init_reg(u32 port,u32 ms) //可以任意传参数进来
{
r_printf("%s\n",__func__);
timering_ms = ms;
u32 u_clk = 24000000;
int div=15;
int index=15;
for(index=15;index >= 0;index--)
{
for(div=15;div >= 1;div--)
{
r_printf("index:%d div:%d\n",index,div);
u32 gpcnt_gts = 32*(1<<index);
if(((timer_div[div])*((gpcnt_gts*timering_ms)/1000)) > 0){
u32 complete_remainder = (u_clk/timer_div[div])%((gpcnt_gts*1000)/timering_ms);
u32 port_remainder = ((gpcnt_gts*1000)%timering_ms);
u32 integer = (u_clk/timer_div[div])/((gpcnt_gts*1000)/timering_ms);
printf("%d %d %d %d\n",gpcnt_gts,complete_remainder,port_remainder,integer);
if((!complete_remainder)&&(!port_remainder)&&(integer >= 0x10))
{
request_irq(IRQ_GPC_IDX,3, gpcnt_isr, 0);
GPCNT_GSS(GPCNT_GSS_INPUT_CH2);
printf("JL_GPCNT->CON 0x%x\n",JL_GPCNT->CON);
GPCNT_CSS(GPCNT_GSS_INPUT_CH3);
printf("JL_GPCNT->CON 0x%x\n",JL_GPCNT->CON);
INPUT_CHANNLEL2_SRC_SEL(8);
INPUT_CHANNLEL3_SRC_SEL(6);
gpio_set_fun_input_port(port, PFI_GP_ICH6);
gpio_set_direction(port, 1);
gpio_set_die(port, 1);
gpio_set_pull_up(port, 0);
gpio_set_pull_down(port, 1);
GPCNT_TIMER->CON = 0;
GPCNT_TIMER->CON |= (0b10 << 2); //时钟源选择STD_24M时钟源
SFR(GPCNT_TIMER->CON, 4, 4,div); //时钟源再分频
GPCNT_TIMER->CNT = 0; //清计数值
GPCNT_TIMER->PRD = (u_clk/timer_div[div])/((gpcnt_gts*1000)/timering_ms); //设置周期
GPCNT_TIMER->PWM = (GPCNT_TIMER->PRD * 5000) / 10000; //设置初始占空比,0~10000对应0~100%
GPCNT_TIMER->CON |= (0b01 << 0); //计数模式
GPCNT_TIMER->CON |= BIT(8); //PWM使能
GPCNT_GTS(index);
printf("JL_GPCNT->CON 0x%x\n",JL_GPCNT->CON);
timering_ms = 1000000/((((u_clk/timer_div[div])/integer)*1000)/gpcnt_gts);
printf("actual_timering_ms == %d\n",timering_ms); //实际设置时间以这里为准
GPCNT_EN(1);
printf("JL_GPCNT->CON 0x%x\n",JL_GPCNT->CON);
return;
}
}
}
}
printf("没有找到合适的参数,请修改时间\n"); //最好就是2 4 8 16之类时间,否则会出现实际时间不准
}
void gpcnt_uninit_reg(void)
{
GPCNT_EN(0);
GPCNT_CLR_PEND(TRUE);
unrequest_irq(IRQ_GPC_IDX);
gpio_disable_fun_input_port(PFI_GP_ICH6);
GPCNT_TIMER->CON |= (0b00 << 0); //关闭定时器
}
LVD电压作用域 烧写器烧写的LVD挡位对应较高的LVD档位
ini文件配置的LVD电压是重新调整的LVD档位
CRC CRC校验(循环冗余校验)是数据通讯中最常采用的校验方式.
默认sdk会开放对应api接口调用,crc16(crc_get_16bit) crc32(crc_get_32bit)


PDM MIC 【金山文档】 AC695N_PDM_MIC
https://kdocs.cn/l/ckj1KbsL4EgzIO中断
701N系列SDK,利用定时器捕捉实现IO中断
参考路径:cpu\br28\mcpwm.c
void io_ext_interrupt_test(void)
{
set_io_ext_interrupt_cbfun(my_io_isr_cbfun);
io_ext_interrupt_init(0, IO_PORTA_04, 1);
io_ext_interrupt_init(1, IO_PORTB_01, 1);
io_ext_interrupt_init(2, IO_PORTC_02, 1);
io_ext_interrupt_init(3, IO_PORTG_01, 1);
extern void wdt_clear();
while (1) {
wdt_clear();
}
}
- 功放PA控制 【金山文档】 功放控制
功放控制.otl - 遥控器EV1527解码 相关资料:扩展库使用说明——无线发送1527编码 - 单片机论坛,单片机技术交流论坛 - 21ic电子技术开发论坛

FAQ
1、AC700N_earphone_release_V1.3.3-直线三键滑动触摸demo(zf)
【金山文档】 AC700N_earphone_release_V1.3.3-直线三键滑动触摸demo
https://kdocs.cn/l/ckuBIiiP1Kas
2、红外发送案例,软中断delay方式
如下是红外发送案例,使用软中断配合timer0 delay的方式,注意timer0的是否已经被系统占用,如果是就换用其他的记得中断优先级设置。
【金山文档】 红外发送参考ir_send_soft
红外发送参考ir_send_soft.otl
本文档为保密信息,未经授权,请勿转发或告知第三方。非常感谢!
This electronic documentation and any attachments are confidential and may be legally privileged or otherwise protected from disclosure. If you are not the intended recipient, please do not disclose the contents to anyone。
Thank you for your cooperation!
解析权归杰理所有!
The analysis right belongs to JieLi Technology Co., Ltd.