Distributeur de nourriture
pour petits animaux de compagnie
avec un Microcontrôleur ATmega8

Ce distributeur me permet de m'absenter un ou deux jours en laissant mon lapin nain seul à la maison. Quatre petites doses de nourriture (granulets secs) basculent à tour de rôle dans un entonnoir prolongé par un tube en PVC jusqu'à la petite gamelle, à des heures et jours présélectionnés. Tel qu'il est programmé ici, il fait basculer la première dose le soir du premier jour (le matin je donne manuellement à manger avant de partir), la deuxième dose le matin du deuxième jour, la troisième le soir du deuxième jour et la quatrième le matin du troisième jour. Je dois rentrer au plus tard avant le soir du troisième jour. De quoi donc passer trois jours et deux nuits consécutives hors de chez moi, en prévoyant bien sûr de fournir suffisamment à boire pour cette durée.

Il serait possible de concevoir un distributeur pour une durée plus longue, mais je le déconseille vivement parce que laisser seul son petit compagnon plus longtemps tient de la maltraitance envers les animaux.

J'alimente cet apppareil avec une petite batterie 12V et non par le secteur afin d'éviter un gros problème en cas de coupure de courant. Je l'ai déjà utilisé ponctuellement et il m'a donné entière satisfaction (ainsi apparemment qu'à Fripounette) mais j'envisage d'installer en parallèle une surveillance à distance par webcam tellement j'étais angoissé pendant mes escapades.

1 La partie mécanique:

Quatre petites doses de nourriture (granulets secs) basculent à tour de rôle dans un entonnoir prolongé par un tube en PVC jusqu'à la petite gamelle, à des heures et jours présélectionnés.

Le basculement des petites boites est déclenché par la rotation précise d'un arbre à cames, actionné par un "servo" du type de ceux qu'on utilise en robotique ou aéromodélisme.

2 La carte électronique

3 Le schéma

Un microcontrôleur ATmega8, un afficheur LCD 2 lignes, quelques boutons poussoirs pour la mise à l'heure et un servomoteur. Rien de plus simple, c'est le programme qui fait tout.

4 Principe et mise en oeuvre


  • Le programme comprend essentiellement une horloge temps réel et un générateur de signaux PPM pour piloter un servomoteur.
  • Le servomoteur qui fait tourner un arbre à cames (petits crochets)  place celui-ci dans des positions angulaires précises.
  • Chaque position angulaire libère une dose basculante (petite boite de "Tic Tac" s'cusez la pub...)
  • Quatre boutons poussoirs permettent la mise à l'heure (plus et moins des heures, plus et moins des minutes).
  • Un bouton poussoir permet de faire avancer manuellement le cycle des positions angulaires afin de régler la mécanique, et de réarmer le tout au départ.
  • Un afficheur LCD 2x16 indique le jour, l'heure, le mode (manuel, auto).
  • Un connecteur de programmation in-situ rend très faciles toutes modifications du soft.
Les heures de déclenchement (6h du matin et 6h du soir) sont modifiables dans le programme, mais pas en "live"; Cela ne me pose pas de problème.
La particularité de cette réalisation réside dans le programme: il est écrit directement en PASCAL. Je vous fournis le code source destiné à être compilé avec le soft AVRco ( E-LAB-Computers ).

5 Le programme

CODE SOURCE en langage Pascal
  1. program distri_graines;
  2.  
  3. {$NOSHADOW}
  4. { $W+ Warnings} {Warnings off}
  5.  
  6. Device = mega8, VCC = 5;
  7.  
  8. Import SysTick, LCDport, RTclock, ServoPort;
  9.  
  10. From System Import;
  11.  
  12. From RTclock Import RTCtimer, RTCalarm;
  13.  
  14. Define
  15. ProcClock = 16000000; {Hertz}
  16. SysTick = 10, Timer2; {msec} // ATTENTION: UTILISE LE Timer2 pour l'horloge RTC
  17. StackSize = 100, iData; // remarque: le prog utilise 44 bytes sur les 100 alloués (voir affi en runtime)
  18. FrameSize = $0020, iData;
  19. LCDport = PortD;
  20. LCDtype = 44780;
  21. LCDrows = 2; {rows}
  22. LCDcolumns = 16; {columns per line}
  23. RTclock = iData, DateTime; {Time, DateTime}
  24. RTCsource = SysTick {, adj}; { adj = +/-100}
  25. RTCtimer = 4;
  26.  
  27. // SysTick = 10; {msec}
  28. // StackSize = $0020, iData;
  29. // FrameSize = $0010, iData;
  30. ServoPort = PortC, 0, iData; {use Portx, startbit n, RAMpage}
  31. ServoChans = 1, positive; {channel count, pulse polarity}
  32. ServoNeutral = 2.0, Timer1; {msec neutral pulse width, Timer used}
  33.  
  34.  
  35. Define_usr
  36. bouton_inc_h = %00100000;
  37. bouton_dec_h = %00010000;
  38. bouton_inc_mn = %00001000;
  39. bouton_dec_mn = %00000100;
  40. bouton_manuel = %00000010;
  41.  
  42. Implementation
  43.  
  44. {$IDATA}
  45.  
  46. {--------------------------------------------------------------}
  47. { Type Declarations }
  48. type
  49. tWeekDays = (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday);
  50.  
  51. tWeekDArr = array[tWeekDays] of string[10];
  52.  
  53. {--------------------------------------------------------------}
  54. { Const Declarations }
  55. const
  56. cWeekDArr : tWeekDArr = ('Dimanche',
  57. 'Lundi',
  58. 'Mardi',
  59. 'Mercredi',
  60. 'Jeudi',
  61. 'Vendredi',
  62. 'Samedi');
  63.  
  64. p1 : integer = 90;
  65. p2 : integer = 60;
  66. p3 : integer = 30;
  67. p4 : integer = 0;
  68. p5 : integer = -30;
  69.  
  70.  
  71. {--------------------------------------------------------------}
  72. { Var Declarations }
  73. {$IDATA}
  74. var
  75. delay : SysTimer8;
  76. ticked : boolean;
  77. timeout : boolean;
  78. timeout1 : boolean;
  79. alarm : boolean;
  80. bool : boolean;
  81. // i, j : byte;
  82.  
  83. j1, cyc1 : byte;
  84. h1, m1, s1 : byte;
  85. stk1 : word;
  86.  
  87.  
  88. {--------------------------------------------------------------}
  89. { functions }
  90.  
  91. procedure init_ports; // ports perso
  92. begin
  93. ddrc:= ddrc and %11000001; // 5 entrées pour les boutons
  94. end;
  95.  
  96.  
  97. procedure RTCtimer(chan : byte); // CallBack from RTCtimer
  98. begin
  99. {
  100.   for i:= 0 to 10 do
  101.   inc(j);
  102.   endfor;
  103.  
  104.   if chan = 0 then
  105.   timeout:= true;
  106.   endif;
  107. }
  108. end;
  109.  
  110.  
  111. procedure RTCalarm; // CallBack from RTCalarm
  112. begin
  113. alarm:= true;
  114. end;
  115.  
  116. procedure RTCtickSecond; // CallBack from RTClock
  117. begin
  118. ticked:= true;
  119. end;
  120.  
  121. procedure RTCtickMinute; // CallBack from RTClock
  122. begin
  123. cyc1:=0; // mode auto
  124. end;
  125.  
  126. procedure RTCtickHour; // CallBack from RTClock
  127. begin
  128. end;
  129.  
  130. procedure MAJ_LCD;
  131. begin
  132. // Write(LCDout, ' ' + cWeekDArr[tWeekDays(RTCgetWeekDay)]);
  133.  
  134. LCDxy(0, 0);
  135. LCDclrEol;
  136.  
  137. // date
  138. LCDxy(0, 0);
  139. Write(LCDout, 'jour=' + ByteToStr(j1 : 2 : ' ') );
  140.  
  141. LCDxy(10, 0);
  142. if cyc1=0 then Write(LCDout, 'AUTO');
  143. else Write(LCDout, 'manu');
  144. endif;
  145.  
  146.  
  147. { test et affiche la pile (stack) en runtime
  148.   stk1:= GetStackFree;
  149.   LCDxy(8, 0);
  150.   Write(LCDout, 'stack=' + IntToStr(stk1 : 2 : '0') );
  151. }
  152.  
  153. LCDclrEol;
  154.  
  155. // Write(LCDout, ByteToStr(RTCgetDay : 2 : '0') + '.');
  156. // Write(LCDout, ByteToStr(RTCgetMonth : 2 : '0') + '.');
  157. // Write(LCDout, '20' + ByteToStr(RTCgetYear : 2 : '0') + ' ');
  158.  
  159. // time
  160. LCDxy(0, 1);
  161. Write(LCDout, ByteToStr(RTCgetHour : 2 : '0') + ':');
  162. Write(LCDout, ByteToStr(RTCgetMinute : 2 : '0') + ':');
  163. Write(LCDout, ByteToStr(RTCgetSecond : 2 : '0'));
  164. end;
  165.  
  166. {--------------------------------------------------------------}
  167. { Main Program }
  168. {$IDATA}
  169.  
  170. begin
  171. init_ports;
  172.  
  173. j1:= 1;
  174. cyc1:=0; //mode auto
  175.  
  176. RTCsetSecond(0);
  177. RTCsetMinute(0);
  178. RTCsetHour(0);
  179.  
  180. RTCsetDay(21);
  181. RTCsetWeekDay(02);
  182. RTCsetMonth(08);
  183. RTCsetYear(07);
  184.  
  185. // LCDCharSet(#0, $0E, $1F, $04, $0E, $11, $1D, $15, $0E);
  186. // LCDCharSet(#1, $00, $0E, $15, $17, $11, $0E, $00, $00);
  187.  
  188. LCDclr; { clear display }
  189. LCDcursor(false, false); { display on, cursor off & no blink }
  190. // Write(LCDout, ' '+#0+' E-LAB RTC Demo '+#1);
  191. ticked:= true;
  192. {
  193.   RTCalarm_Time(00, 01, 25); // 00:01:25
  194.   RTCalarm_Date(01, 01, 01); // 01.01.2001
  195.   RTCalarm_Start(2); // 0 = stop, 1 = time, 2 = date+time
  196.  
  197.   RTCtimer_Load(0, 15);
  198.   RTCtimer_Start(0);
  199.   RTCtimer_Load(1, 10);
  200.   RTCtimer_Start(1);
  201. }
  202. EnableInts;
  203.  
  204. SetServoChan(0, p1);
  205.  
  206. loop
  207. if ticked then
  208. ticked:= false;
  209.  
  210. MAJ_LCD;
  211.  
  212. mdelay(100);
  213.  
  214. h1:= RTCgetHour;
  215. m1:= RTCgetMinute;
  216. s1:= RTCgetSecond;
  217. if (h1 = 23 ) and (m1 = 59) and (s1 = 59 )then j1:= j1 + 1; endif;
  218.  
  219. if (j1 = 1) and (h1 = 18 ) and (m1 = 0) then SetServoChan(0, p2); endif;
  220. if (j1 = 2) and (h1 = 6 ) and (m1 = 0) then SetServoChan(0, p3); endif;
  221. if (j1 = 2) and (h1 = 18 ) and (m1 = 0) then SetServoChan(0, p4); endif;
  222. if (j1 = 3) and (h1 = 6 ) and (m1 = 0) then SetServoChan(0, p5); endif;
  223.  
  224. endif;
  225.  
  226.  
  227. // SET TIME
  228. if (PinC and bouton_inc_mn ) = 0 then
  229. disableInts;
  230. RTCsetSecond(0);
  231. RTCsetMinute(RTCgetMinute + 1);
  232. EnableInts;
  233. MAJ_LCD;
  234. while (PinC and bouton_inc_mn ) = 0 do mdelay(10); endwhile; // wait relache + anti-rebond
  235. j1:= 1;
  236. endif;
  237.  
  238. if (PinC and bouton_dec_mn ) = 0 then
  239. disableInts;
  240. RTCsetSecond(0);
  241. RTCsetMinute(RTCgetMinute - 1);
  242. EnableInts;
  243. MAJ_LCD;
  244. while (PinC and bouton_dec_mn ) = 0 do mdelay(10); endwhile; // wait relache + anti-rebond
  245. j1:= 1;
  246. endif;
  247.  
  248. if (PinC and bouton_inc_h ) = 0 then
  249. disableInts;
  250. RTCsetSecond(0);
  251. RTCsetHour(RTCgetHour + 1);
  252. EnableInts;
  253. MAJ_LCD;
  254. while (PinC and bouton_inc_h ) = 0 do mdelay(10); endwhile; // wait relache + anti-rebond
  255. j1:= 1;
  256. endif;
  257.  
  258. if (PinC and bouton_dec_h ) = 0 then
  259. disableInts;
  260. RTCsetSecond(0);
  261. RTCsetHour(RTCgetHour - 1);
  262. EnableInts;
  263. MAJ_LCD;
  264. while (PinC and bouton_dec_h ) = 0 do mdelay(10); endwhile; // wait relache + anti-rebond
  265. j1:= 1;
  266. endif;
  267.  
  268. if (PinC and bouton_manuel ) = 0 then
  269. cyc1:=cyc1+1;
  270. if cyc1>4 then SetServoChan(0, p1); cyc1:=0; endif;
  271. case cyc1 of
  272. 0: nop; |
  273. 1: SetServoChan(0, p2); |
  274. 2: SetServoChan(0, p3); |
  275. 3: SetServoChan(0, p4); |
  276. 4: SetServoChan(0, p5); |
  277. endcase;
  278.  
  279.  
  280. while (PinC and bouton_manuel ) = 0 do mdelay(10); endwhile; // wait relache + anti-rebond
  281. j1:= 1;
  282. endif;
  283.  
  284.  
  285.  
  286. endloop;
  287.  
  288. RTCtimer_Stop(0);
  289. end distri_graines.
  290.  
  291.  
  292.  
  293.  
  294.  

6 Documents techniques


7 -

L'ensemble de la mécanique et de l'électronique ainsi que la batterie doivent bien sûr être placés hors d'atteinte de la bestiole, en dehors de la cage.
Je précise que mon lapin à accès à toute la maison en ma présence, et à toute une pièce en mon absence (toutefois tout ce qui peut présenter un quelquonque danger, produits, ordinateur, plaque de cuisson, prises et fils électriques, se trouve hors d'atteinte!) depuis six ans (en 2012), et qu'il n'y a jamais eu le moindre problème.
Il convient bien entendu de tester l'ensemble sur un cycle complet en restant à la maison, avant de s'absenter pour de bon !

Tenez, la voici Fripounette...

8 -

9 -



16961