ALIMENTATION 20V - 5A à découpage,
numérique, pilotée par un ATmega8

Cette réalisation est la suite logique de l'alimentation 20V à découpage, analogique, décrite sur ce site, qui me donne satisfaction depuis des années... sauf en ce qui concerne le crachement du potentiomètre de réglage de la tension. En audio un potentiomètre qui crache ça fait du bruit, dans une alim ça fait fluctuer la tension!

Cette nouvelle alimentation digitale ne peut être concernée par ce problème. La tension est réglable par sauts de 0.1V de 0V à 20V. La saisie de la valeur de la consigne se fait au moyen de quatre boutons: deux pour déplacer un curseur de droite à gauche (sur l'affichage) et deux pour incrémenter/décrémenter le digit correspondant.

1 Le schéma

Attention à la tension de service des divers condensateurs de filtrage... 40V mini, même en aval du MOSFET, sinon risque d'explosion... (si, suite à un plantage totalement imprévu du programme, le MOSFET venait à rester en conduction permanente). Cette éventualité bien "qu'interdite" doit tout de même être prise en compte et ne doit pas aboutir à une détérioration des composants!

L'alimentation 5V de la partie logique est obtenue par un module abaisseur de tension à découpage (LM2596 DC-DC adjustable power step-down module) en vente sur eBay (pas par moi !)

2 Le tranfo torique + fusibles et le redressement

Il va sans dire que les fusibles sont OBLIGATOIRES vu la puissance du transfo torique.

3 Les boutons:

Comme déjà dit plus haut, la saisie de la valeur de la consigne se fait au moyen de quatre boutons: deux pour déplacer un curseur de droite à gauche (sur l'affichage) et deux pour incrémenter/décrémenter le digit correspondant.

4 Analyse du schéma

J'ai choisi, pour le découpage, d'utiliser un MOSFET canal N qui offre une résistance RDS(on) plus faible (0.028 ohm) qu'un canal P courant.

J'ai voulu placer ce MOSFET dans la branche positive de l'alim, pour des raison de simplification de la mesure de la tension de sortie, ce qui oblige à l'utiliser en montage drain-commun. Mais dans cette configuration la saturation du transistor n'est pas obtenue si on se contente d'amener la gate au potentiel du drain: il faut une tension positive supérieure à celle du drain pour conserver une valeur de tension gate-source suffisante pour saturer le transistor lorsque la source est ramenée au potentiel du drain. Cette tension peut être obtenue de diverses manières, petit transfo d'impulsions sur le signal de commande, survolteur par un doubleur de tension capacitif alimenté sur le 5V (40106 + capas + diodes), deuxième transfo d'alim, "pompage" capacitif depuis la tension de source ...

-le transfo d'impulsion sur le signal de commande a l’inconvénient de poser des problèmes de respect de la forme du signal pour de grandes amplitudes et un rapport cyclique très dissymétrique (saturation du circuit magnétique).

J'ai opté pour une prise intermédiaire sur le transfo torique principal (pourvu d'origine de plusieurs sorties au secondaire, ce qui n'ajoutait qu'une diode et un condensateur au montage), le circuit principal étant alimenté en +24V et le circuit de polarisation en +32V.

Pour que le rendement énergétique soit bon (que le MOSFET ne dissipe pas) le découpage doit s'effectuer à fronts raides. Or un MOSFET a une capacité Cgs importante. Pour obtenir un signal bien rectangulaire sur la source j'ai utilisé un montage push-pull (Q2 PNP- Q3 NPN en montage "collecteur commun"), car une simple résistance de rappel ne convient pas dans ce cas, sauf à lui donner une valeur bien trop faible provoquant une consommation trop importante.
Le transistor driver Q1 assure l'amplitude du signal de commande.

Pour l'explication du fonctionnement du découpage (attaque du MOSFET par un signal rectangulaire à rapport cyclique ajustable, diode de "roue libre" etc...) je vous renvoie à la description de l'alimentation 20V analogique.

5 Le code source du firmware en langage C:

CODE SOURCE en assembleur PIC
  1. /*==============================================================================
  2. par Silicium628
  3. derniere mise à jour 18 juillet 2013
  4. ================================================================================
  5. ATmega8
  6. Pilotage d une alimentation a decoupage 20V
  7. -Affichage LCD 2 x 16 caractères
  8. ================================================================================
  9. IMPORTANT: programmer les FUSES BODLEVEL et BODEN (Brown out level detector) afin de fiabiliser le RESET à l'allumage
  10.  
  11. */
  12.  
  13.  
  14. #define F_CPU 16000000
  15.  
  16. #include <avr/io.h>
  17. #include <util/delay.h>
  18.  
  19. #include "dm_lcd.c" // l'attribution des pins/ports pour le LCD (et le type de LCD) est faite dans le fichier "dm_lcd.h"
  20.  
  21. #define bouton_L 0b00000001
  22. #define bouton_U 0b00000010
  23. #define bouton_D 0b00000100
  24. #define bouton_R 0b00001000
  25.  
  26.  
  27. char * version = "4.2";
  28.  
  29.  
  30. /*
  31. RAPPEL variables avr-gcc (vérifiable avec le .map)
  32.  
  33. char 1 -128 .. 127 ou caractères
  34. unsigned char 1 0 .. 255 (equiv à byte du pascal)
  35. uint8_t 1 (c'est la même chose que l'affreux 'unsigned char')
  36. char toto[n] n
  37. int 2 -32768 .. 32767
  38. int16_t 2 idem 'int'
  39. short int 2 pareil que int (?)
  40. unsigned int 2 0 .. 65535
  41. uint16_t 2 idem 'unsigned int'
  42. long int 4 -2 147 483 648 à 2 147 483 647
  43. int32_t 4 32 bits ; idem long int
  44. unsigned long long 64-bit unsigned type.
  45. uint64_t 8 64-bit unsigned type.
  46. long long int 8
  47. unsigned long int 4 32 bits ; 0 .. 4 294 967 295 (4,2 x 10^9)
  48. float 4
  49. double 4 // (?)
  50.  
  51. La déclaration char JOUR[7][9];
  52. réserve l'espace en mémoire pour 7 mots contenant 9 caractères (dont 8 caractères significatifs).
  53. */
  54.  
  55. uint16_t consigne_V;
  56. uint8_t digits_consigne_V[3]; // 0..199 pour 0V.. a 19.9V
  57. uint16_t I_max;
  58. uint16_t mesure_V;
  59. uint16_t memo1_V;
  60. uint16_t memo2_V;
  61. uint16_t memo3_V;
  62. uint16_t memo4_V;
  63.  
  64. uint16_t mesure_I;
  65. uint8_t pos;
  66. uint8_t pos_curseur;
  67. uint8_t boutons_I;
  68. uint8_t boutons_V;
  69. uint8_t memo_boutons_V;
  70. uint16_t compteur1;
  71.  
  72.  
  73. void init_ports (void) // ports perso
  74. // 0 = entree, 1=sortie ; les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
  75. {
  76. PORTB = 0b00000000;
  77. DDRB |= 0b00000010; // portB[1] = sortie (OC1A = sortie PWM 16 bits du timer1)
  78.  
  79. DDRC = 0b11001111; //PC4 en entree (ADC4 - mesure du courant); PC5 en entree (ADC5 - mesure de la tension)
  80.  
  81. DDRD = 0b11000000; //portD[0..3] en entree (4 boutons) ; portD[4..5] en entree (4 boutons sur 2 bits)
  82. PORTD = 0b00111111;
  83. }
  84.  
  85.  
  86. void InitADC (void)
  87. {
  88. ADCSRA = _BV(ADEN) | _BV(ADPS2); // Activate ADC with Prescaler 16 --> 1Mhz/16 = 62.5kHz
  89. ADMUX |= 0b11000101; //Bit 7:6 – REFS1:0: ADC Reference Selection Bits =11 -> Internal 2.56V Voltage Reference with external capacitor at AREF pin ; Bits 0:3 - Analog Channel Selection Bits
  90. // ici Select pin ADC5 using MUX avec ref tension interne = 2.56V
  91.  
  92. }
  93.  
  94. void InitINTs (void)
  95. {
  96. GICR |= 0b00000000; // gere les INTs voir page 67 du pdf
  97. MCUCR |= 0b00000010; // The falling edge of INT0 generates an interrupt request. p:67 du pdf
  98. }
  99.  
  100.  
  101. void InitPWM (void)
  102. {
  103. TCCR1A |= (1 << COM1A1); // set none-inverting mode
  104.  
  105. // TCCR1A |= (1 << WGM11) | (1 << WGM10); // set 10bit phase corrected PWM Mode
  106. TCCR1A |= (1 << WGM13) | (1 << WGM12) | (1 << WGM11) | (1 << WGM10); // set 16bit phase corrected PWM Mode
  107.  
  108. // TCCR1B |= (1 << CS11); // set prescaler to 8 and starts PWM
  109. TCCR1B |= (1 << CS10); // set no prescaler and starts PWM
  110.  
  111. OCR1A = 65535;
  112. }
  113.  
  114.  
  115. void init_variables(void)
  116. {
  117. // OCR2=255;
  118.  
  119. digits_consigne_V[0] = 0;
  120. digits_consigne_V[1] = 5; // 5V
  121. digits_consigne_V[2] = 0;
  122.  
  123. mesure_V=0;
  124. memo1_V=0;
  125. memo2_V=0;
  126. memo3_V=0;
  127. memo4_V=0;
  128.  
  129. }
  130.  
  131.  
  132. void lcd_gotoxy_clrEOL (int x, int y)
  133. // place le curseur en x,y et efface jusqu'a la fin de la ligne
  134. {
  135. lcd_gotoxy(x, y);
  136. uint8_t i;
  137. for (i=x; i<20; i++)
  138. { lcd_puts(" "); }
  139. lcd_gotoxy(x, y);
  140. }
  141.  
  142.  
  143.  
  144. void lcd_aff_nb (uint16_t valeur, uint8_t nb_chiffres, uint8_t nb_decimales)
  145. {
  146. //affiche un nombre en representation decimale
  147. unsigned char r ;
  148. char tbl[7];
  149. uint8_t i;
  150.  
  151. for (i=1; i<=nb_chiffres; i++)
  152. {
  153. r=48 + valeur % 10; // modulo (reste de la division)
  154. valeur /= 10; // quotient
  155. tbl[i]=r;
  156. }
  157. for (i=1; i<=nb_chiffres; i++)
  158. {
  159. if (i== (nb_chiffres - nb_decimales +1) ) { lcd_puts("."); }
  160. lcd_putc(tbl[nb_chiffres +1 -i]);
  161. }
  162. }
  163.  
  164.  
  165.  
  166. void lcd_aff_bin (unsigned long int valeur, int nb_digits)
  167. {
  168. //affiche un nombre en representation binaire
  169. // 16 bits max
  170. unsigned char r ;
  171. char tbl[17];
  172. uint8_t i;
  173.  
  174. for (i=1; i<=nb_digits; i++)
  175. {
  176. r= 48 + valeur % 2; // modulo (reste de la division)
  177. valeur /= 2; // quotient
  178. tbl[i]=r;
  179. };
  180.  
  181. for (i=1; i<=nb_digits; i++)
  182. {
  183. lcd_putc(tbl[nb_digits +1 -i]);
  184. }
  185. }
  186.  
  187.  
  188. void affiche_mesure_V(void)
  189. {
  190. // void lcd_aff_nb (uint16_t valeur, uint8_t nb_chiffres, uint8_t nb_decimales)
  191. lcd_gotoxy_clrEOL (6,0);
  192. lcd_puts("mesu:");
  193. lcd_aff_nb(mesure_V, 3, 1);
  194. lcd_puts("V");
  195. lcd_gotoxy(pos_curseur,0);
  196. }
  197.  
  198.  
  199. void affiche_mesure_I(valeur)
  200. {
  201. lcd_gotoxy_clrEOL (0,1);
  202. lcd_puts("I=");
  203. lcd_aff_nb(valeur, 4, 0);
  204. lcd_puts("mA");
  205. lcd_gotoxy(pos_curseur,0);
  206. }
  207.  
  208.  
  209. void affiche_consigne_I(void)
  210. {
  211. uint16_t i_A;
  212. i_A = I_max / 1000;
  213. lcd_gotoxy_clrEOL (6,0);
  214. lcd_puts("Imax=");
  215. lcd_aff_nb(i_A, 1, 0);
  216. lcd_puts("A");
  217. lcd_gotoxy(pos_curseur,0);
  218. }
  219.  
  220.  
  221.  
  222.  
  223. void affiche_consigne_V(void)
  224. {
  225. uint8_t c, i, p;
  226.  
  227.  
  228. lcd_gotoxy_clrEOL (0,0);
  229.  
  230. for (i=0; i<=2; i++)
  231. {
  232. p=2-i;
  233. c=48+digits_consigne_V[p];
  234. if ((p==2) & digits_consigne_V[p] ==0 )
  235. {
  236. lcd_putc(' '); // n'affiche pas le premier 0 non sinificatif
  237. }
  238. else {lcd_putc(c); }
  239. if (p==1) {lcd_putc('.');}
  240. }
  241. lcd_puts("V");
  242.  
  243. pos_curseur =2-pos;
  244. if (pos_curseur>1) {pos_curseur++;}
  245. lcd_gotoxy(pos_curseur,0);
  246. }
  247.  
  248.  
  249.  
  250. void calcul_consigne_V()
  251. {
  252. consigne_V=digits_consigne_V[0]+10*digits_consigne_V[1]+100*digits_consigne_V[2];
  253. }
  254.  
  255.  
  256.  
  257. void acqui_tension(void)
  258. {
  259. ADMUX = 0b11000101; // Select pin ADC5 using MUX - ref tension interne = 2.56V
  260. ADCSRA |= _BV(ADSC); //Start conversion - resolution 10bits
  261. while (ADCSRA & _BV(ADSC) ) {} // attend la fin de la converstion
  262. // ADCW = 0..1024 pour Vin = 0..2V56
  263.  
  264. mesure_V = ADCW;
  265.  
  266.  
  267. }
  268.  
  269.  
  270. void acqui_courant(void)
  271. {
  272. ADMUX = 0b11000100; // Select pin ADC4 using MUX - ref tension interne = 2.56V
  273. ADCSRA |= _BV(ADSC); //Start conversion - resolution 10bits
  274. while (ADCSRA & _BV(ADSC) ) {} // attend la fin de la converstion
  275.  
  276. mesure_I = (894-ADCW)*16; // en mA ; lit la valeur convertie ; ADCW = 0..1024 pour Vin = 0..2V56
  277. }
  278.  
  279.  
  280. void lit_boutons_I(void)
  281. {
  282. boutons_I = PIND & 0b00110000;
  283. if (boutons_I == 0b00000000) {I_max = 4000; }
  284. if (boutons_I == 0b00010000) {I_max = 3000; }
  285. if (boutons_I == 0b00100000) {I_max = 2000; }
  286. if (boutons_I == 0b00110000) {I_max = 1000; }
  287. }
  288.  
  289.  
  290.  
  291. void lit_boutons_V(void)
  292. {
  293. calcul_consigne_V();
  294. memo_boutons_V = boutons_V;
  295. boutons_V = PIND & 0b00001111;
  296.  
  297. if (boutons_V != memo_boutons_V)
  298. {
  299. //--------------------------------
  300. // position du curseur (digit actif)
  301. if( (boutons_V & bouton_L) == 0)
  302. {
  303. pos++;
  304. if (pos>2) { pos=2;}
  305. }
  306. if( (boutons_V & bouton_R) == 0)
  307. {
  308. if (pos>0) { pos--;}
  309. }
  310. //--------------------------------
  311. // edition du digit actif
  312.  
  313. //--------------- UP -----------------
  314. if ( (boutons_V & bouton_U) == 0)
  315. {
  316. digits_consigne_V[pos]++;
  317. if (digits_consigne_V[pos]>9)
  318. {
  319. digits_consigne_V[pos]=0;
  320. digits_consigne_V[pos+1]++;
  321. if (digits_consigne_V[pos+1]>9)
  322. {
  323. digits_consigne_V[pos+1]=0;
  324. digits_consigne_V[pos+2]++;
  325. }
  326. }
  327. calcul_consigne_V();
  328. if (consigne_V > 200)
  329. {
  330. digits_consigne_V[0]=0;
  331. digits_consigne_V[1]=0;
  332. digits_consigne_V[2]=2;
  333. calcul_consigne_V();
  334. }
  335. }
  336. //--------------- DOWN -----------------
  337. if (( (boutons_V & bouton_D) == 0) & (consigne_V > 0) )
  338. {
  339. if (digits_consigne_V[pos]>0)
  340. {
  341. digits_consigne_V[pos]--;
  342. }
  343. else if (digits_consigne_V[pos]==0)
  344. {
  345. if (digits_consigne_V[pos+1]>0)
  346. {
  347. digits_consigne_V[pos+1]--;
  348. digits_consigne_V[pos]=9;
  349. }
  350. else if (digits_consigne_V[pos+2]>0)
  351. {
  352. digits_consigne_V[pos+2]--;
  353. digits_consigne_V[pos+1]=9;
  354. digits_consigne_V[pos]=9;
  355. }
  356. }
  357. }
  358. calcul_consigne_V();
  359. if (consigne_V > 200) // possible ici par debordement lors de la decrementation
  360. {
  361. digits_consigne_V[0]=0;
  362. digits_consigne_V[1]=0;
  363. digits_consigne_V[2]=0;
  364. }
  365. calcul_consigne_V();
  366. affiche_consigne_V();
  367.  
  368.  
  369. }
  370. while ( (PIND & 0b00001111) != 0b00001111) { ;} // boucle en attendant le relachement du bouton
  371.  
  372. }
  373.  
  374.  
  375.  
  376.  
  377. void test (void)
  378. {
  379. lcd_clrscr();
  380. lcd_puts("TEST");
  381. }
  382.  
  383.  
  384.  
  385. int main (void)
  386. {
  387. init_variables();
  388. init_ports();
  389. InitADC();
  390. // InitINTs(); non utilisees
  391. InitPWM();
  392.  
  393. OCR1A = 65535;
  394.  
  395. lcd_init(LCD_DISP_ON_CURSOR);
  396. lcd_clrscr();
  397. lcd_home();
  398. lcd_puts("ALIMENTATION 20V");
  399. lcd_gotoxy(0,1);
  400. lcd_puts("version ");
  401. lcd_puts(version);
  402.  
  403. _delay_ms(2000);
  404.  
  405.  
  406. lcd_clrscr();
  407.  
  408. calcul_consigne_V();
  409. affiche_consigne_V();
  410.  
  411. uint16_t q1;
  412.  
  413.  
  414.  
  415. while(1)
  416. {
  417. lit_boutons_V();
  418. lit_boutons_I();
  419. acqui_tension();
  420. acqui_courant();
  421.  
  422. //ici il faut comparer [mesure_V] a [consigne_V]
  423. //consigne_V = 0..200
  424. //mesure_V = 0..1000 (par ajustement du pont diviseur resistif externe)
  425.  
  426. q1= (uint16_t) (mesure_V / 5);
  427. //q1 = 0..200
  428.  
  429. // asservissement du rapport cyclique du signal OCR1A et donc de la tension
  430.  
  431. if (mesure_I < I_max) // cas ou il n'y a pas de dépassement du courant max admissible:
  432. {
  433. if ((q1 < consigne_V) & (OCR1A>0)) { OCR1A -= 1; }
  434. if ((q1 > consigne_V) & (OCR1A<65535)) { OCR1A += 1; }
  435. _delay_ms(10);
  436. }
  437. else // limitation en courant
  438. {
  439. if (OCR1A<65535) {OCR1A += 1;} // fait chuter l'angle de conduction
  440. if((mesure_I - I_max) < 500) { _delay_ms(5); } // très vite pour les grands dépassements puis
  441. // plus lentement pour les 500 derniers mA sinon la tension devient fluctuante.
  442. }
  443.  
  444.  
  445. compteur1++;
  446. if (compteur1>=100)
  447. {
  448. affiche_mesure_I(mesure_I);
  449. affiche_consigne_I();
  450. compteur1=0;
  451. }
  452.  
  453.  
  454. }
  455. }
  456.  
  457.  

6 Explication de quelques points du soft:

La tension ainsi que le courant sont acquis par le convertisseur analogique-numérique (ADC 10bits) de l'ATmega8. Ce dernier est configuré avec comme tension de référence la référence interne = 2.56V

Le signal PWM de commande du MOSFET est généré par le timer1 en configuration de résolution temporelle sur 16bits:

code:
"TCCR1A |= (1 << WGM13) | (1 << WGM12) | (1 << WGM11) | (1 << WGM10); // set 16bit phase corrected PWM Mode"

Lors de la phase de RESET (qui peut durer... un certain temps surtout si l'on s'endort avec le doigt appuyé sur le bouton "reset") tous les pins d'E/S de l'ATmega8 se placent en mode haute impédance. Leur potentiel est alors déterminé par la connectique. C'est la raison de la présence d'une résistance de 10k câblée entre le pin15 (PB1 = sortie OC1A : signaux PWM) et le +Vcc 5V, afin de bloquer le MOSFET. Sans cette résistance, l'alim délivrerait 32V pendant la phase de reset!!

L'attribution des pins/ports pour le LCD (et le type de LCD) est faite dans le fichier "dm_lcd.h"

7 La limitation en courant

La limitation du courant de sortie est obtenue par logiciel. L'acquisition de l'intensité est réalisée par un capteur à effet hall (UGN 3503) inséré dans une fente pratiquée dans un tore en ferrite sur lequel sont bobinées 30 spires parcourues par le courant.

Toute la difficulté réside dans la réalisation de la fente dans la ferrite. La meilleure des scies à métaux ne tient pas plus d'une minute, après quoi elle est bonne à couper le beurre.

Un disque miniature de perceuse voit son diamètre se réduire tout aussi vite...

La solution: une petite fraise diamantée, visible sur la photo ci-contre.

8 -

Le capteur UGN 3503 à effet Hall est maintenu dans la fente par l'épaisseur d'un morceau de gaine thermorétractable transparente (solution démontable, la transparence de la gaine permettra aux générations futures de lire facilement le type du composant utilisé ;)

9 Le circuit imprimé

10 -

Le circuit imprimé est en cours de conception (avec le logiciel libre Kicad sous Linux Mint). Les "gros" composants (transfo, pont de diodes, selfs toriques, condensateurs électrochimiques, Mosfet et radiateur) restent en dehors de la carte, ils seront directement fixés dans le boîtier. Le circuit imprimé proposé ici n'a pas encore été réalisé, donc prudence... Je vous tiendrai au courant ici dès qu'il sera fini et aura donné satisfaction.

11 Documents:


12 -

A voir également...



Me contacter à propos de cet article :

Question mathématique :

Click to reload image
=
cliquez sur l'image pour un faire autre calcul.




Réponses à certaines de vos questions...
8841